Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
Starting a CSS rule with `>` breaks styling without producing an error
### Describe the bug

Starting a CSS rule with `>` at the  `<style>` root breaks the CSS rules from that point, without producing an error. Consequently, the developer is let wondering why those CSS rules have no effect.

I don't see how a child combinator (`>`) could be valid at a component root, so this should probably generate an error. The same is true for the sibling combinator (`+`), but that one at least already produces a `Unused CSS selector` warning, and does not result in the following CSS rules to be dropped.

### Reproduction

See https://svelte.dev/repl/b8551c675fbd4f4e9b174d3f11000512?version=3.48.0

Notice the broken CSS output:
```
> >
```

### Logs

_No response_

### System Info

```shell
Svelte v3.48.0
```


### Severity

annoyance


Discussion:

Resolving patch:
diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts
--- a/src/compiler/compile/compiler_errors.ts
+++ b/src/compiler/compile/compiler_errors.ts
@@ -234,6 +234,10 @@ export default {
 		code: 'css-invalid-global-selector',
 		message: ':global(...) must contain a single selector'
 	},
+	css_invalid_selector: (selector: string) => ({
+		code: 'css-invalid-selector',
+		message: `Invalid selector "${selector}"`
+	}),
 	duplicate_animation: {
 		code: 'duplicate-animation',
 		message: "An element can only have one 'animate' directive"
diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts
--- a/src/compiler/compile/css/Selector.ts
+++ b/src/compiler/compile/css/Selector.ts
@@ -145,6 +145,7 @@ export default class Selector {
 		}
 
 		this.validate_global_with_multiple_selectors(component);
+		this.validate_invalid_combinator_without_selector(component);
 	}
 
 	validate_global_with_multiple_selectors(component: Component) {
@@ -163,6 +164,17 @@ export default class Selector {
 			}
 		}
 	}
+	validate_invalid_combinator_without_selector(component: Component) {
+		for (let i = 0; i < this.blocks.length; i++) {
+			const block = this.blocks[i];
+			if (block.combinator && block.selectors.length === 0) {
+				component.error(this.node, compiler_errors.css_invalid_selector(component.source.slice(this.node.start, this.node.end)));
+			}
+			if (!block.combinator && block.selectors.length === 0) {
+				component.error(this.node, compiler_errors.css_invalid_selector(component.source.slice(this.node.start, this.node.end)));
+			}
+		}
+	}
 
 	get_amount_class_specificity_increased() {
 		let count = 0;


Test patch:
diff --git a/test/validator/samples/css-invalid-combinator-selector-1/errors.json b/test/validator/samples/css-invalid-combinator-selector-1/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-1/errors.json
@@ -0,0 +1,9 @@
+[
+	{
+		"code": "css-invalid-selector",
+		"message": "Invalid selector \"> span\"",
+		"start": { "line": 10, "column": 1, "character": 88 },
+		"end": { "line": 10, "column": 7, "character": 94 },
+		"pos": 88
+	}
+]
diff --git a/test/validator/samples/css-invalid-combinator-selector-1/input.svelte b/test/validator/samples/css-invalid-combinator-selector-1/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-1/input.svelte
@@ -0,0 +1,13 @@
+<p><span /></p>
+
+<style>
+	p > span {
+		color: red;
+	}
+	:has(> span) {
+		color: red;
+	}
+	> span {
+		color: red;
+	}
+</style>
diff --git a/test/validator/samples/css-invalid-combinator-selector-2/errors.json b/test/validator/samples/css-invalid-combinator-selector-2/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-2/errors.json
@@ -0,0 +1,9 @@
+[
+	{
+		"code": "css-invalid-selector",
+		"message": "Invalid selector \"+ p\"",
+		"start": { "line": 8, "column": 1, "character": 68 },
+		"end": { "line": 8, "column": 4, "character": 71 },
+		"pos": 68
+	}
+]
diff --git a/test/validator/samples/css-invalid-combinator-selector-2/input.svelte b/test/validator/samples/css-invalid-combinator-selector-2/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-2/input.svelte
@@ -0,0 +1,11 @@
+<p><span /></p>
+<p><span /></p>
+
+<style>
+	p + p {
+		color: red;
+	}
+	+ p {
+		color: red;
+	}
+</style>
diff --git a/test/validator/samples/css-invalid-combinator-selector-3/errors.json b/test/validator/samples/css-invalid-combinator-selector-3/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-3/errors.json
@@ -0,0 +1,9 @@
+[
+	{
+		"code": "css-invalid-selector",
+		"message": "Invalid selector \"> span\"",
+		"start": { "line": 5, "column": 2, "character": 44 },
+		"end": { "line": 5, "column": 8, "character": 50 },
+		"pos": 44
+	}
+]
diff --git a/test/validator/samples/css-invalid-combinator-selector-3/input.svelte b/test/validator/samples/css-invalid-combinator-selector-3/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-3/input.svelte
@@ -0,0 +1,9 @@
+<p><span /></p>
+
+<style>
+	@media screen {
+		> span {
+			color: red;
+		}
+	}
+</style>
diff --git a/test/validator/samples/css-invalid-combinator-selector-4/errors.json b/test/validator/samples/css-invalid-combinator-selector-4/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-4/errors.json
@@ -0,0 +1,9 @@
+[
+	{
+		"code": "css-invalid-selector",
+		"message": "Invalid selector \"p >\"",
+		"start": { "line": 4, "column": 1, "character": 26 },
+		"end": { "line": 4, "column": 4, "character": 29 },
+		"pos": 26
+	}
+]
diff --git a/test/validator/samples/css-invalid-combinator-selector-4/input.svelte b/test/validator/samples/css-invalid-combinator-selector-4/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/css-invalid-combinator-selector-4/input.svelte
@@ -0,0 +1,7 @@
+<p><span /></p>
+
+<style>
+	p > {
+		color: red;
+	}
+</style>