Repository: sveltejs/eslint-plugin-svelte
Pull request #1063 (2025-02-09 10:38:56)

feat: detect version information from `node_modules` for pnpm’s catalog
I noticed this issue while using pnpm catalog in our project.

github-actions[bot]: <!-- posted by pkg.pr.new-comment.mjs -->

## Try the Instant Preview in Online Playground

[ESLint Online Playground](https://eslint-online-playground.netlify.app/?overrideDeps=%7B%22eslint-plugin-svelte%22%3A%22https%3A%2F%2Fpkg.pr.new%2Feslint-plugin-svelte%401063%22%7D#eslint-plugin-svelte%20with%20typescript)

## Install the Instant Preview to Your Local

```
npm i https://pkg.pr.new/eslint-plugin-svelte@1063
```

## Published Instant Preview Packages:

- eslint-plugin-svelte: https://pkg.pr.new/eslint-plugin-svelte@1063

[View Commit](https://github.com/sveltejs/eslint-plugin-svelte/commit/dc10495a4378ee38ad43df6a8581918a30c68382)
changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 3510a298787dcdabf0648d07dc3e0518bf4ede65

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                 | Type  |
| -------------------- | ----- |
| eslint-plugin-svelte | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/sveltejs/eslint-plugin-svelte/new/feat/support-pnpm-catalog?filename=.changeset/hungry-deers-collect.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afeat%3A%20detect%20version%20information%20from%20node_modules%20when%20not%20specified%20in%20%60package.json%60%2C%20like%20pnpm%E2%80%99s%20catalog%0A)


baseballyama: I have confirmed that it works correctly in my project using pnpm catalog.

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/.changeset/two-poets-wait.md b/.changeset/two-poets-wait.md
new file mode 100644
index 0000000..ff9a23c
--- /dev/null
+++ b/.changeset/two-poets-wait.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat: detect version information from node_modules when not specified in package.json, like pnpm’s catalog
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 e6fb687..6a67983 100644
--- a/docs-svelte-kit/package.json
+++ b/docs-svelte-kit/package.json
@@ -16,8 +16,8 @@
     "@babel/types": "^7.26.0",
     "@fontsource/fira-mono": "^5.1.0",
     "@ota-meshi/eslint-plugin": "^0.17.6",
-    "@shikijs/markdown-it": "^2.0.0",
-    "@shikijs/twoslash": "^2.0.0",
+    "@shikijs/markdown-it": "^3.0.0",
+    "@shikijs/twoslash": "^3.0.0",
     "@sindresorhus/slugify": "^2.2.1",
     "@sveltejs/adapter-static": "^3.0.6",
     "@sveltejs/kit": "^2.8.5",
@@ -45,7 +45,7 @@
     "eslint-scope": "^8.2.0",
     "eslint-visitor-keys": "^4.2.0",
     "espree": "^10.3.0",
-    "globals": "^15.12.0",
+    "globals": "^16.0.0",
     "markdown-it-anchor": "^9.2.0",
     "markdown-it-container": "^4.0.0",
     "markdown-it-emoji": "^3.0.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/prefer-class-directive.md b/docs/rules/prefer-class-directive.md
index ab68bb5..5b5c6a1 100644
--- a/docs/rules/prefer-class-directive.md
+++ b/docs/rules/prefer-class-directive.md
@@ -41,7 +41,7 @@ You cannot enforce this style by using [prettier-plugin-svelte]. That is, this r
 
 ```json
 {
-  "svelte/html-quotes": [
+  "svelte/prefer-class-directive": [
     "error",
     {
       "prefer": "empty" // or "always"
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 f2761f2..c7a02eb 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.20.0",
+    "eslint": "~9.21.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 c92be3b..a3de4f9 100644
--- a/packages/eslint-plugin-svelte/package.json
+++ b/packages/eslint-plugin-svelte/package.json
@@ -86,7 +86,7 @@
     "assert": "^2.1.0",
     "esbuild": "^0.25.0",
     "eslint-scope": "^8.2.0",
-    "eslint-typegen": "^1.0.0",
+    "eslint-typegen": "^2.0.0",
     "eslint-visitor-keys": "^4.2.0",
     "espree": "^10.3.0",
     "less": "^4.2.1",
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-prop-names-in-kit-pages.ts b/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts
index ab3f9d5..95cbe49 100644
--- a/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts
+++ b/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts
@@ -3,7 +3,6 @@ import type { TSESTree } from '@typescript-eslint/types';
 import { createRule } from '../utils/index.js';
 import type { RuleContext } from '../types.js';
 import { getSvelteVersion } from '../utils/svelte-context.js';
-import { getFilename } from '../utils/compat.js';
 
 const EXPECTED_PROP_NAMES = ['data', 'errors', 'form', 'snapshot'];
 const EXPECTED_PROP_NAMES_SVELTE5 = [...EXPECTED_PROP_NAMES, 'children'];
@@ -49,7 +48,7 @@ export default createRule('valid-prop-names-in-kit-pages', {
 	},
 	create(context) {
 		let isScript = false;
-		const isSvelte5 = getSvelteVersion(getFilename(context)) === '5';
+		const isSvelte5 = getSvelteVersion() === '5';
 		const expectedPropNames = isSvelte5 ? EXPECTED_PROP_NAMES_SVELTE5 : EXPECTED_PROP_NAMES;
 		return {
 			// <script>
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/get-node-module.ts b/packages/eslint-plugin-svelte/src/utils/get-node-module.ts
new file mode 100644
index 0000000..9b2a445
--- /dev/null
+++ b/packages/eslint-plugin-svelte/src/utils/get-node-module.ts
@@ -0,0 +1,71 @@
+import fs from 'fs';
+import path from 'path';
+import { createCache } from './cache.js';
+
+const isRunOnBrowser = !fs.readFileSync;
+const nodeModuleCache = createCache<string | null>();
+const nodeModulesCache = createCache<string[]>();
+
+/**
+ * Find package directory in node_modules
+ */
+function findPackageInNodeModules(dir: string, packageName: string): string | null {
+	if (isRunOnBrowser) return null;
+
+	const nodeModulesPath = path.join(dir, 'node_modules');
+	const packagePath = path.join(nodeModulesPath, packageName);
+
+	try {
+		const stats = fs.statSync(packagePath);
+		if (stats.isDirectory()) {
+			return packagePath;
+		}
+	} catch {
+		// ignore if directory not found
+	}
+
+	return null;
+}
+
+/**
+ * Get first found package path from node_modules
+ */
+export function getNodeModule(packageName: string, startPath = 'a.js'): string | null {
+	if (isRunOnBrowser) return null;
+
+	const cacheKey = `${startPath}:${packageName}`;
+	const cached = nodeModulesCache.get(cacheKey);
+	if (cached) {
+		return cached[0] ?? null;
+	}
+
+	const startDir = path.dirname(path.resolve(startPath));
+	let dir = startDir;
+	let prevDir = '';
+
+	do {
+		// check cache
+		const cachePath = nodeModuleCache.get(`${dir}:${packageName}`);
+		if (cachePath) {
+			if (cachePath !== null) {
+				nodeModulesCache.set(cacheKey, [cachePath]);
+				return cachePath;
+			}
+		} else {
+			// search new
+			const packagePath = findPackageInNodeModules(dir, packageName);
+			nodeModuleCache.set(`${dir}:${packageName}`, packagePath);
+			if (packagePath) {
+				nodeModulesCache.set(cacheKey, [packagePath]);
+				return packagePath;
+			}
+		}
+
+		// go to parent
+		prevDir = dir;
+		dir = path.resolve(dir, '..');
+	} while (dir !== prevDir);
+
+	nodeModulesCache.set(cacheKey, []);
+	return null;
+}
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[];
diff --git a/packages/eslint-plugin-svelte/src/utils/svelte-context.ts b/packages/eslint-plugin-svelte/src/utils/svelte-context.ts
index e821e3a..e4cb124 100644
--- a/packages/eslint-plugin-svelte/src/utils/svelte-context.ts
+++ b/packages/eslint-plugin-svelte/src/utils/svelte-context.ts
@@ -2,8 +2,10 @@ import type { RuleContext } from '../types.js';
 import fs from 'fs';
 import path from 'path';
 import { getPackageJsons } from './get-package-json.js';
+import { getNodeModule } from './get-node-module.js';
 import { getFilename, getSourceCode } from './compat.js';
 import { createCache } from './cache.js';
+import { VERSION as SVELTE_VERSION } from 'svelte/compiler';
 
 const isRunInBrowser = !fs.readFileSync;
 
@@ -168,51 +170,37 @@ function getSvelteKitContext(
 	return result;
 }
 
-const svelteVersionCache = createCache<SvelteContext['svelteVersion']>();
-
-export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion'] {
-	const cached = svelteVersionCache.get(filePath);
-	if (cached) return cached;
+function checkAndSetSvelteVersion(version: string): SvelteContext['svelteVersion'] | null {
+	const major = extractMajorVersion(version, false);
+	if (major == null) {
+		return null;
+	}
+	if (major === '3' || major === '4') {
+		return '3/4';
+	}
+	return major as SvelteContext['svelteVersion'];
+}
 
+export function getSvelteVersion(): SvelteContext['svelteVersion'] {
 	// Hack: if it runs in browser, it regards as Svelte project.
 	if (isRunInBrowser) {
-		svelteVersionCache.set(filePath, '5');
 		return '5';
 	}
 
-	try {
-		const packageJsons = getPackageJsons(filePath);
-		for (const packageJson of packageJsons) {
-			const version = packageJson.dependencies?.svelte ?? packageJson.devDependencies?.svelte;
-			if (typeof version !== 'string') {
-				continue;
-			}
-			const major = extractMajorVersion(version, false);
-			if (major === '3' || major === '4') {
-				svelteVersionCache.set(filePath, '3/4');
-				return '3/4';
-			}
-			svelteVersionCache.set(filePath, major as SvelteContext['svelteVersion']);
-			return major as SvelteContext['svelteVersion'];
-		}
-	} catch {
-		/** do nothing */
-	}
-
-	svelteVersionCache.set(filePath, null);
-	return null;
+	return checkAndSetSvelteVersion(SVELTE_VERSION);
 }
 
 const svelteKitVersionCache = createCache<SvelteContext['svelteKitVersion']>();
 
-/**
- * Check givin file is under SvelteKit project.
- *
- * If it runs on browser, it always returns true.
- *
- * @param filePath A file path.
- * @returns
- */
+function checkAndSetSvelteKitVersion(
+	version: string,
+	filePath: string
+): SvelteContext['svelteKitVersion'] {
+	const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion'];
+	svelteKitVersionCache.set(filePath, major);
+	return major;
+}
+
 function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'] {
 	const cached = svelteKitVersionCache.get(filePath);
 	if (cached) return cached;
@@ -223,27 +211,42 @@ function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'
 		return '2';
 	}
 
+	const nodeModule = getNodeModule('@sveltejs/kit', filePath);
+	if (nodeModule) {
+		try {
+			const packageJson = JSON.parse(
+				fs.readFileSync(path.join(nodeModule, 'package.json'), 'utf8')
+			);
+			const result = checkAndSetSvelteKitVersion(packageJson.version, filePath);
+			if (result != null) {
+				return result;
+			}
+		} catch {
+			/** do nothing */
+		}
+	}
+
 	try {
 		const packageJsons = getPackageJsons(filePath);
-		if (packageJsons.length === 0) return null;
-		if (packageJsons[0].name === 'eslint-plugin-svelte') {
-			// Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
-			// So always it returns 2 if it runs on the package.
-			svelteKitVersionCache.set(filePath, '2');
-			return '2';
-		}
+		if (packageJsons.length > 0) {
+			if (packageJsons[0].name === 'eslint-plugin-svelte') {
+				// Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
+				// So always it returns 2 if it runs on the package.
+				svelteKitVersionCache.set(filePath, '2');
+				return '2';
+			}
 
-		for (const packageJson of packageJsons) {
-			const version =
-				packageJson.dependencies?.['@sveltejs/kit'] ??
-				packageJson.devDependencies?.['@sveltejs/kit'];
-			if (typeof version !== 'string') {
-				svelteKitVersionCache.set(filePath, null);
-				return null;
+			for (const packageJson of packageJsons) {
+				const version =
+					packageJson.dependencies?.['@sveltejs/kit'] ??
+					packageJson.devDependencies?.['@sveltejs/kit'];
+				if (typeof version === 'string') {
+					const result = checkAndSetSvelteKitVersion(version, filePath);
+					if (result != null) {
+						return result;
+					}
+				}
 			}
-			const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion'];
-			svelteKitVersionCache.set(filePath, major);
-			return major;
 		}
 	} catch {
 		/** do nothing */
@@ -291,7 +294,7 @@ export function getSvelteContext(context: RuleContext): SvelteContext | null {
 	if (cached) return cached;
 
 	const svelteKitContext = getSvelteKitContext(context);
-	const svelteVersion = getSvelteVersion(filePath);
+	const svelteVersion = getSvelteVersion();
 	const svelteFileType = getSvelteFileType(filePath);
 
 	if (svelteVersion == null) {


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..fade069
--- /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 .div-class/35"'
+  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..5067576
--- /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 .div-class/35"'
+  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'));