eslint-plugin-svelte icon indicating copy to clipboard operation
eslint-plugin-svelte copied to clipboard

Svelte 5: When extracting props from the `$props` rune typescript eslint errors with unsafe assignment, even though the type is defined following the documentation

Open victoragcosta opened this issue 1 year ago • 3 comments

Before You File a Bug Report Please Confirm You Have Done The Following...

  • [X] I have tried restarting my IDE and the issue persists.
  • [X] I have updated to the latest version of the packages.

What version of ESLint are you using?

9.5.0

What version of eslint-plugin-svelte are you using?

2.40.0

What did you do?

Configuration
import tsParser from "@typescript-eslint/parser";
import svelteParser from "svelte-eslint-parser";
import ts from "typescript-eslint";
import tsPlugin from "@typescript-eslint/eslint-plugin"
import svelteConfig from "./svelte.config.mjs";

export default ts.config(
	{
		ignores: [
			// Root JS and TS (not included in tsconfig.json and I'm lazy)
			"*.{js,cjs,mjs}",
			"*.{ts,cts,mts}",
		],
		languageOptions: {
			parser: tsParser,
			parserOptions: {
				project: true, 
				// `programs` was giving some weird errors even though I wasn't using this config
				programs: false,
				tsconfigRootDir: import.meta.dirname,
				extraFileExtensions: [".svelte"],
			},
		},
		rules: {
			'@typescript-eslint/no-unsafe-assignment': 'error'
		},
		plugins: {
			'@typescript-eslint': tsPlugin,
		}
	},
	{
		files: ["**/*.svelte", "*.svelte"],
		languageOptions: {
			parser: svelteParser,
			parserOptions: {
				parser: tsParser,
				svelteConfig,
			},
		},
	},
	{
		ignores: [
			// IDEs
			".vscode/*",
			"!.vscode/extensions.json",
			"!.vscode/settings.json",
			// Autogenerated files
			".svelte-kit",
			".DS_Store",
			"node_modules",
			"/build",
			"/package",
			"*.local",
			// Lock files
			"pnpm-lock.yaml",
			"package-lock.json",
			"yarn.lock",
			"bun.lockb",
		],
	},
);
<script lang="ts">
	import type { Snippet } from "svelte";

	interface LayoutProps {
		children: Snippet;
	}

	// This generates the error Unsafe assignment of an `any` value. [@typescript-eslint/no-unsafe-assignment]
	let { children }: LayoutProps = $props();
</script>

{@render children()}

I created a new SvelteKit project and configured it with Svelte 5 to use runes. I used Bun as my package manager and runtime. After that I tried to create a layout file and use runes mode, but when setting up the $props so the layout could accept children I wasn't able to type it the same way that the Svelte's documentation uses it. I can work around it by adding as LayoutProps after $props(), but this feels wrong considering the documentation didn't explicitly say to typecast. I have also tested with non-route components and it also happens.

What did you expect to happen?

I expected that when I typed the $props rune exactly as informed by the Svelte 5 preview documentation I would get no errors of unsafe any assignments.

What actually happened?

/home/victor/pessoal/keyboard-visualizer/src/lib/TestComponent.svelte
  9:6  error  Unsafe assignment of an `any` value  @typescript-eslint/no-unsafe-assignment

/home/victor/pessoal/keyboard-visualizer/src/routes/+layout.svelte
  9:6  error  Unsafe assignment of an `any` value  @typescript-eslint/no-unsafe-assignment

✖ 2 problems (2 errors, 0 warnings)

error: "eslint" exited with code 1

It detected getting the props for the component as an unsafe assignment.

Link to GitHub Repo with Minimal Reproducible Example

https://github.com/victoragcosta/eslint-plugin-svelte-props-unsafe-assignment-repro

Additional comments

Edit: I have tested it now and this also happens if I use npm and node instead of bun.

victoragcosta avatar Jun 20 '24 20:06 victoragcosta

A workaround right now is to use a type assertion.

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface LayoutProps {
		children: Snippet;
	}

	const { children } = $props() as LayoutProps;
</script>

{@render children()}

Once this is fixed and the types generated by Svelte are used then this workaround should be removed.

<script lang="ts">
	const { children } = $props();
</script>

{@render children()}

kkirkfield avatar Aug 14 '24 16:08 kkirkfield

If I do the workaround, I get Typescript errors. The types Svelte generates (visible if you run svelte-package are all like this:

props: Record<string, never>;

That causes errors like this (seen through svelte-check):

Type 'boolean' is not assignable to type 'never'.ts(2322)

Props have the right types if I don't use runes or don't assert types.

CollierCZ avatar Aug 30 '24 20:08 CollierCZ

Thank you for posting this issue!

Hmm. It seems like a bug but the rule seems to work fine since it's actually any. I don't have any ideas yet how to work around it in this plugin :thinking:

ota-meshi avatar Sep 13 '24 03:09 ota-meshi

I know this is more of a quetion for the Svelte team, but couldn't $props() be made generic? So you would do something like $props<Props>() and that would return the Props type?

marekdedic avatar Oct 30 '24 09:10 marekdedic

I forgot to comment here, but this issue may be resolved by updating this plugin and svelte-eslint-parser. I confirmed that using the latest version of the parser in the repository shared by @victoragcosta resolved the issue. https://github.com/victoragcosta/eslint-plugin-svelte-props-unsafe-assignment-repro

Anyone with the same problem could you please try it?


I know this is more of a quetion for the Svelte team, but couldn't $props() be made generic? So you would do something like $props<Props>() and that would return the Props type?

svelte-eslint-parser now uses types like $props<Props>(): Props instead of $props type defined in Svelte core. It seems to work fine with the eslint plugin. I think it might be better for svelte itself to use $props<Props>(): Props as well, but I'm not sure 😅

ota-meshi avatar Oct 31 '24 01:10 ota-meshi

The reason why we don' use generics is https://github.com/sveltejs/svelte/pull/10694

baseballyama avatar Oct 31 '24 22:10 baseballyama

I think it's good to force users to write it like this:

let { message }: { message: string } = $props();

However, because type inference works, we can still write that even if we define the type with $props<Props>(): Props.

For the ESLint plugin, the rule works well if type inference works on the return type of $props(), but I don't know if it will be useful for other Svelte language tools.

ota-meshi avatar Nov 01 '24 02:11 ota-meshi

I'm not sure where it was corrected but I think now we don't get this error.

baseballyama avatar Jan 02 '25 03:01 baseballyama