Repository: sveltejs/eslint-plugin-svelte (ts)

Issue:
Add rule checking for successful style parsing
### Motivation

With the recent edition of a rule that uses the parsed styles (#489), this rule in fact doesn't produce any errors if the styles cannot be parsed (either they're invalid, or `svelte-eslint-parser` cannot parse the used style language)

### Description

I propose a rule that would produce a warning in either of these 2 cases (parse error / unknown language). This would guarantee that rules using styles (currently only `no-unused-class-name`) work correctly. There's also a minor overlap with the `block-lang` rule, but I think this is OK

### Examples

```svelte
<!-- ✓ GOOD -->
<style>
  .class {
    font-weight: bold;
  }
</style>

<style lang="scss">
  .class {
    font-weight: bold;
  }
</style>

<!-- ✗ BAD -->
<style>
  .class
    font-weight: bold;
</style>

<style lang="unknown">
  .class {
    font-weight: bold;
  }
</style>
```


### Additional comments

_No response_

Pull request description:
feat: implemented the `valid-style-parse` rule
Closes #509


Patch:
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 54865cc..0d4d959 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -1,5 +1,5 @@
 {
-  "mode": "pre",
+  "mode": "exit",
   "tag": "next",
   "initialVersions": {
     "eslint-plugin-svelte": "2.46.0",
diff --git a/.changeset/stupid-penguins-smoke.md b/.changeset/stupid-penguins-smoke.md
new file mode 100644
index 0000000..8be998d
--- /dev/null
+++ b/.changeset/stupid-penguins-smoke.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat: added the valid-style-parse rule
diff --git a/README.md b/README.md
index 01eb6bd..83a17b9 100644
--- a/README.md
+++ b/README.md
@@ -335,6 +335,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [svelte/require-store-callbacks-use-set-param](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | store callbacks must use `set` param |  |
 | [svelte/require-store-reactive-access](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-reactive-access/) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :star::wrench: |
 | [svelte/valid-compile](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. |  |
+| [svelte/valid-style-parse](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-style-parse/) | require valid style element parsing |  |
 
 ## Security Vulnerability
 
diff --git a/docs-svelte-kit/package.json b/docs-svelte-kit/package.json
index 830321a..e6fb687 100644
--- a/docs-svelte-kit/package.json
+++ b/docs-svelte-kit/package.json
@@ -39,7 +39,7 @@
     "assert": "^2.1.0",
     "cross-spawn": "^7.0.6",
     "env-cmd": "^10.1.0",
-    "esbuild": "^0.24.0",
+    "esbuild": "^0.25.0",
     "escape-html": "^1.0.3",
     "eslint-plugin-svelte": "workspace:^",
     "eslint-scope": "^8.2.0",
diff --git a/docs/migration.md b/docs/migration.md
index 10cd9b6..0acc972 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -62,3 +62,99 @@ Therefore, you need to replace the package name, and the presets, rules, and set
     +    "svelte": { ... }
       },
     ```
+
+## From `eslint-plugin-svelte` v2 To v3
+
+This section explains the necessary changes when upgrading from `eslint-plugin-svelte` v2 to v3.  
+v3 includes **support for ESLint Flat Config only**, **changes to the recommended rule set**, and other breaking changes.
+
+---
+
+## Breaking Changes
+
+### 1. **Minimum Node.js Version Requirement**
+
+v3 requires **one of the following Node.js versions**:
+
+- `^18.20.4`
+- `^20.18.0`
+- `>=22.10.0`
+
+### 2. **ESLint Flat Config Only**
+
+- `.eslintrc.js` and `.eslintrc.json` are **no longer supported**.
+- You must use **Flat Config (`eslint.config.js`)**.
+- See [README](README.md) for more details.
+
+### 3. **ESLint Version Requirement**
+
+- v3 requires **ESLint 8.57.1 or later** (including v9.x).
+
+### 4. **Changes to Recommended Rule Set**
+
+- The following rules are now included in `recommended`:
+  - `svelte/infinite-reactive-loop`
+  - `svelte/no-dom-manipulating`
+  - `svelte/no-dupe-on-directives`
+  - `svelte/no-reactive-reassign`
+  - `svelte/require-event-dispatcher-types`
+  - Many others (See [Changelog](https://github.com/sveltejs/eslint-plugin-svelte/releases/tag/eslint-plugin-svelte%403.0.0-next.16) for more details)
+- `svelte/valid-compile` has been **removed** from `recommended`.
+
+### 5. **Deprecated Rules**
+
+- `svelte/no-dynamic-slot-name` is **deprecated**.
+- `svelte/no-goto-without-base` is **deprecated** and replaced with `svelte/no-navigation-without-base`.
+
+---
+
+## New Features & Improvements
+
+### 1. **Support for Svelte 5**
+
+- Rules such as `no-not-function-handler` and `valid-prop-names-in-kit-pages` now support **Svelte 5**.
+
+### 2. **New Rules**
+
+- `consistent-selector-style`: Enforce consistent selector styles.
+- `no-useless-children-snippet`: Prevent unnecessary `{@children}` usage.
+- `prefer-const`: Prefer `const` over `let`.
+- `no-navigation-without-base`: Ensure proper usage of `goto()`.
+
+---
+
+## Migration Steps
+
+### 1. **Upgrade Package**
+
+```sh
+npm install eslint-plugin-svelte@3
+```
+
+### 2. **Check Node.js Version**
+
+```sh
+node -v
+```
+
+- Ensure it is `v18.20.4` or later.
+
+### 3. **Check ESLint Version**
+
+```sh
+npx eslint -v
+```
+
+- Ensure it is `8.57.1` or later (or `9.x`).
+
+### 4. **Use Flat Config (`eslint.config.js`)**
+
+- Remove `.eslintrc.js` and migrate to `eslint.config.js`.
+
+### 5. **Apply the Updated Recommended Rules**
+
+- If using `recommended`, check the impact of newly added rules.
+
+### 6. **Replace Deprecated Rules**
+
+- Replace `no-goto-without-base` with `no-navigation-without-base`.
diff --git a/docs/rules.md b/docs/rules.md
index 5e509b0..3d6000e 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -32,6 +32,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | store callbacks must use `set` param                                                                                                   |                |
 | [svelte/require-store-reactive-access](./rules/require-store-reactive-access.md)                 | disallow to use of the store itself as an operand. Need to use $ prefix or get function.                                               | :star::wrench: |
 | [svelte/valid-compile](./rules/valid-compile.md)                                                 | disallow warnings when compiling.                                                                                                      |                |
+| [svelte/valid-style-parse](./rules/valid-style-parse.md)                                         | require valid style element parsing                                                                                                    |                |
 
 ## Security Vulnerability
 
diff --git a/docs/rules/valid-style-parse.md b/docs/rules/valid-style-parse.md
new file mode 100644
index 0000000..eee703f
--- /dev/null
+++ b/docs/rules/valid-style-parse.md
@@ -0,0 +1,78 @@
+---
+pageClass: 'rule-details'
+sidebarDepth: 0
+title: 'svelte/valid-style-parse'
+description: 'require valid style element parsing'
+---
+
+# svelte/valid-style-parse
+
+> require valid style element parsing
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
+
+## :book: Rule Details
+
+This rule reports issues with parsing of the `<style>` element by the svelte-eslint-parser.
+
+<!--eslint-skip-->
+
+```svelte
+<script>
+  /* eslint svelte/valid-style-parse: ["error"] */
+</script>
+
+<!-- ✓ GOOD -->
+<style>
+  .class {
+    font-weight: bold;
+  }
+</style>
+```
+
+```svelte
+<script>
+  /* eslint svelte/valid-style-parse: ["error"] */
+</script>
+
+<!-- ✓ GOOD -->
+<style lang="scss">
+  .class {
+    font-weight: bold;
+  }
+</style>
+```
+
+```svelte
+<script>
+  /* eslint svelte/valid-style-parse: ["error"] */
+</script>
+
+<!-- ✗ BAD -->
+<style>
+  .class
+    font-weight: bold;
+</style>
+```
+
+```svelte
+<script>
+  /* eslint svelte/valid-style-parse: ["error"] */
+</script>
+
+<!-- ✗ BAD -->
+<style lang="unknown">
+  .class {
+    font-weight: bold;
+  }
+</style>
+```
+
+## :wrench: Options
+
+Nothing.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/valid-style-parse.ts)
+- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/valid-style-parse.ts)
diff --git a/package.json b/package.json
index 17bdcf5..f2761f2 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
     "@typescript-eslint/parser": "^8.16.0",
     "c8": "^10.1.2",
     "env-cmd": "^10.1.0",
-    "eslint": "~9.19.0",
+    "eslint": "~9.20.0",
     "eslint-config-prettier": "^10.0.0",
     "eslint-formatter-friendly": "^7.0.0",
     "eslint-plugin-eslint-plugin": "^6.3.2",
diff --git a/packages/eslint-plugin-svelte/package.json b/packages/eslint-plugin-svelte/package.json
index 1cd2608..c92be3b 100644
--- a/packages/eslint-plugin-svelte/package.json
+++ b/packages/eslint-plugin-svelte/package.json
@@ -84,7 +84,7 @@
     "@types/stylus": "^0.48.43",
     "acorn": "^8.14.0",
     "assert": "^2.1.0",
-    "esbuild": "^0.24.0",
+    "esbuild": "^0.25.0",
     "eslint-scope": "^8.2.0",
     "eslint-typegen": "^1.0.0",
     "eslint-visitor-keys": "^4.2.0",
diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts
index 30f15c1..5e38cc0 100644
--- a/packages/eslint-plugin-svelte/src/rule-types.ts
+++ b/packages/eslint-plugin-svelte/src/rule-types.ts
@@ -366,6 +366,11 @@ export interface RuleOptions {
    * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/
    */
   'svelte/valid-prop-names-in-kit-pages'?: Linter.RuleEntry<[]>
+  /**
+   * require valid style element parsing
+   * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-style-parse/
+   */
+  'svelte/valid-style-parse'?: Linter.RuleEntry<[]>
 }
 
 /* ======= Declarations ======= */
diff --git a/packages/eslint-plugin-svelte/src/rules/valid-style-parse.ts b/packages/eslint-plugin-svelte/src/rules/valid-style-parse.ts
new file mode 100644
index 0000000..685c68a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/src/rules/valid-style-parse.ts
@@ -0,0 +1,40 @@
+import { createRule } from '../utils/index.js';
+import { getCwd, getSourceCode } from '../utils/compat.js';
+
+export default createRule('valid-style-parse', {
+	meta: {
+		docs: {
+			description: 'require valid style element parsing',
+			category: 'Possible Errors',
+			recommended: false
+		},
+		schema: [],
+		messages: {},
+		type: 'problem'
+	},
+	create(context) {
+		const sourceCode = getSourceCode(context);
+		if (!sourceCode.parserServices.isSvelte) {
+			return {};
+		}
+		const cwd = `${getCwd(context)}/`;
+
+		return {
+			SvelteStyleElement(node) {
+				const styleContext = sourceCode.parserServices.getStyleContext!();
+				if (styleContext.status === 'parse-error') {
+					context.report({
+						loc: node.loc,
+						message: `Error parsing style element. Error message: "${styleContext.error.message.replace(cwd, '')}"`
+					});
+				}
+				if (styleContext.status === 'unknown-lang') {
+					context.report({
+						loc: node.loc,
+						message: `Found unsupported style element language "${styleContext.sourceLang}"`
+					});
+				}
+			}
+		};
+	}
+});
diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts
index 2a2ddee..13d3bd6 100644
--- a/packages/eslint-plugin-svelte/src/utils/rules.ts
+++ b/packages/eslint-plugin-svelte/src/utils/rules.ts
@@ -72,6 +72,7 @@ import system from '../rules/system.js';
 import validCompile from '../rules/valid-compile.js';
 import validEachKey from '../rules/valid-each-key.js';
 import validPropNamesInKitPages from '../rules/valid-prop-names-in-kit-pages.js';
+import validStyleParse from '../rules/valid-style-parse.js';
 
 export const rules = [
 	typescriptEslintNoUnnecessaryCondition,
@@ -143,5 +144,6 @@ export const rules = [
 	system,
 	validCompile,
 	validEachKey,
-	validPropNamesInKitPages
+	validPropNamesInKitPages,
+	validStyleParse
 ] as RuleModule[];


Test patch:
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-errors.yaml
new file mode 100644
index 0000000..996754a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-errors.yaml
@@ -0,0 +1,6 @@
+- message: 'Error parsing style element. Error message:
+    "tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-input.svelte:4:11:
+    Unknown word"'
+  line: 7
+  column: 1
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-input.svelte
new file mode 100644
index 0000000..a3184db
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-css01-input.svelte
@@ -0,0 +1,18 @@
+<div class="container">
+  <div class="div-class">Hello</div>
+
+  <span class="span-class">World!</span>
+</div>
+
+<style>
+  // This syntax is intentionally invalid CSS - this is to be used to test resiliency against invalid input
+  .container {
+    class .div-class/35
+      # Weird comment
+      color: red;
+
+    .span-class begin
+      font-weight: bold;
+    end
+  }
+</style>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-errors.yaml
new file mode 100644
index 0000000..f13c056
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-errors.yaml
@@ -0,0 +1,6 @@
+- message: 'Error parsing style element. Error message:
+    "tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-input.svelte:4:11:
+    Unknown word"'
+  line: 7
+  column: 1
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-input.svelte
new file mode 100644
index 0000000..df4d586
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/invalid-scss01-input.svelte
@@ -0,0 +1,18 @@
+<div class="container">
+  <div class="div-class">Hello</div>
+
+  <span class="span-class">World!</span>
+</div>
+
+<style lang="scss">
+  // This syntax is intentionally invalid CSS - this is to be used to test resiliency against invalid input
+  .container {
+    class .div-class/35
+      # Weird comment
+      color: red;
+
+    .span-class begin
+      font-weight: bold;
+    end
+  }
+</style>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/unknown-lang01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/unknown-lang01-errors.yaml
new file mode 100644
index 0000000..0ca96bb
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/unknown-lang01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Found unsupported style element language "invalid-lang"
+  line: 7
+  column: 1
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/unknown-lang01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/unknown-lang01-input.svelte
new file mode 100644
index 0000000..43e1850
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/invalid/unknown-lang01-input.svelte
@@ -0,0 +1,18 @@
+<div class="container">
+  <div class="div-class">Hello</div>
+
+  <span class="span-class">World!</span>
+</div>
+
+<style lang="invalid-lang">
+  // This syntax is intentionally invalid CSS - this is to be used to test resiliency against unknown style languages.
+  .container {
+    class .div-class/35
+      # Weird comment
+      color: red;
+
+    .span-class begin
+      font-weight: bold;
+    end
+  }
+</style>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/no-style01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/no-style01-input.svelte
new file mode 100644
index 0000000..f3f9e58
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/no-style01-input.svelte
@@ -0,0 +1,3 @@
+<a href="https://svelte.dev">Hello</a>
+
+<span style="font-weight: bold;">World!</span>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/valid-css01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/valid-css01-input.svelte
new file mode 100644
index 0000000..cca266a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/valid-css01-input.svelte
@@ -0,0 +1,13 @@
+<div class="div-class">Hello</div>
+
+<span class="span-class">World!</span>
+
+<style>
+	.div-class {
+		color: red;
+	}
+
+	.span-class {
+		font-weight: bold;
+	}
+</style>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/valid-scss01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/valid-scss01-input.svelte
new file mode 100644
index 0000000..f7130b5
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-style-parse/valid/valid-scss01-input.svelte
@@ -0,0 +1,18 @@
+<div class="container">
+	<div class="div-class">Hello</div>
+
+	<span class="span-class">World!</span>
+</div>
+
+<style lang="scss">
+	.container {
+		.div-class {
+			// This is an inline comment
+			color: red;
+		}
+
+		.span-class {
+			font-weight: bold;
+		}
+	}
+</style>
diff --git a/packages/eslint-plugin-svelte/tests/src/rules/valid-style-parse.ts b/packages/eslint-plugin-svelte/tests/src/rules/valid-style-parse.ts
new file mode 100644
index 0000000..d5d1c5f
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/src/rules/valid-style-parse.ts
@@ -0,0 +1,12 @@
+import { RuleTester } from '../../utils/eslint-compat.js';
+import rule from '../../../src/rules/valid-style-parse.js';
+import { loadTestCases } from '../../utils/utils.js';
+
+const tester = new RuleTester({
+	languageOptions: {
+		ecmaVersion: 2020,
+		sourceType: 'module'
+	}
+});
+
+tester.run('valid-style-parse', rule as any, loadTestCases('valid-style-parse'));