eslint-plugin-svelte: Linting with flat config (ESLint 9) does not work with TypeScript

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

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

What version of ESLint are you using?

9.0.0

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

2.36.0

What did you do?

Configuration
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintPluginSvelte from 'eslint-plugin-svelte';

export default [
  { ignores: ['.svelte-kit'] },
  { languageOptions: { globals: globals.browser } },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  ...eslintPluginSvelte.configs['flat/recommended'],
];
<script lang="ts">
	let count: number = 0;
</script>

{count}

What did you expect to happen?

The linter to be happy

What actually happened?

 2:11  error  Parsing error: Unexpected token :

Link to GitHub Repo with Minimal Reproducible Example

https://github.com/MathiasWP/eslint-9-svelte-typescript-error

Additional comments

No response

About this issue

  • Original URL
  • State: open
  • Created 3 months ago
  • Comments: 18

Most upvoted comments

For completeness, here is a full eslint “flat file” configuration that works for Svelte projects that use TypeScript. It also includes Prettier linting.

Maybe an example of such a “flat file” config could be added to the doc of eslint-plugin-svelte, because so far there are only examples of the “old” type of config file (if I’m not mistaken).

// eslint.config.cjs

import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintPluginSvelte from 'eslint-plugin-svelte';
import js from '@eslint/js';
import svelteParser from 'svelte-eslint-parser';
import tsEslint from 'typescript-eslint';
import tsParser from '@typescript-eslint/parser';

export default [
  js.configs.recommended,
  ...tsEslint.configs.strict,
  ...eslintPluginSvelte.configs['flat/recommended'],
  eslintPluginPrettierRecommended, // must be last to override conflicting rules.
  {
    rules: {
      semi: ['warn', 'always'],
      quotes: [
        'warn',
        'single',
        { avoidEscape: true, allowTemplateLiterals: true },
      ],
      'no-nested-ternary': 'error',
      'linebreak-style': ['error', 'unix'],
      'no-cond-assign': ['error', 'always'],
      'no-console': 'error',
      '@typescript-eslint/sort-type-constituents': 'error',
      'sort-imports': [
        'error',
        {
          ignoreCase: true,
          ignoreDeclarationSort: false,
          ignoreMemberSort: false,
          memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
          allowSeparatedGroups: true,
        },
      ],
    },
  },
  {
    files: ['**/*.svelte'],
    languageOptions: {
      parser: svelteParser,
      parserOptions: {
        parser: tsParser,
      },
    },
    rules: {
      'svelte/no-target-blank': 'error',
      'svelte/no-at-debug-tags': 'error',
      'svelte/no-reactive-functions': 'error',
      'svelte/no-reactive-literals': 'error',
    },
  },
];

This should be added to the readme in the parser configuration section. We also don’t have to import the ts parser see here https://typescript-eslint.io/packages/typescript-eslint/#manually-configuring-our-plugin-and-parser.

I think that it helps you to specify files for each config object like below:

import globals from 'globals';
import js from '@eslint/js';
import tseslint from 'typescript-eslint' // v7
import prettierConfig from 'eslint-config-prettier';
import svelte from 'eslint-plugin-svelte';
import svelteParser from 'svelte-eslint-parser';

/** @type {import('typescript-eslint').Config} */
export default = [
  ...[
    js.configs.recommended,
    ...tseslint.configs.strictTypeChecked, // typescript-eslint set `['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts']` to `files`
    prettierConfig,  // eslint-config-prettier does not turn 'svelte/*' rules off
    { // overrides options should be after other config
      languageOptions: {
        parser: tseslint.parser,
        parserOptions: {
          sourceType: 'module',
          extraFileExtensions: ['.svelte']
        },
        globals: { ...globals.browser, ...globals.node },
      },
      rules: { /* rules for js/ts/svelte */ } // don't set 'svelte/*' rules here
    }
  ].map(conf => ({ ...conf, files: ['**/*.js', '**/*.ts', '**/*.svelte'] })), // To override `files` is so important!!
  ...svelte.configs['flat/recommended'], // eslint-plugin-svelte set `['*.svelte', '**/*.svelte']` to `files`
  ...svelte.configs['flat/prettier'], // if non svelte files occur 'svelte/*' error, these element should be set `files`
  { // your config should be after
    files: ['*.svelte', '**/*.svelte'], // the same value as eslint-plugin-svelte `files` option
    languageOptions: {
      parser: svelteParser,
      parserOptions: { parser: tseslint.parser }
    },
    // ↓ rule types;  sveltejs/eslint-plugin-svelte #735 
    /** @type {import('eslint').Linter.RulesRecord} */
    rules: { /* rules for svelte */ }
  },
  {
    // other override settings. e.g. for `files: ['**/*.test.*']`
  },
  { ignores: ['node_modules/', /* other ignores */] } // overrides global ignores
];

It because that typescript-eslint set ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'] to files.

[!NOTE] '**/*.js' pattern may be not overridden with '*.js' pattern.

Using typescript-eslint helper function config(...) as below provides the almost same flat-config array as above.

eslint.config.js using `config` helper
import globals from 'globals';
import js from '@eslint/js';
import tseslint from 'typescript-eslint' // v7
import prettierConfig from 'eslint-config-prettier';
import svelte from 'eslint-plugin-svelte';
import svelteParser from 'svelte-eslint-parser';

const defaultConfig = tseslint.config({
  files: ['**/*.js', '**/*.ts', '**/*.svelte'],
  extends: [
    js.configs.recommended, // if it occurs a type error, you can install `@types/eslint__js` package
    ...tseslint.configs.strictTypeChecked,
    prettierConfig // if it occurs a type error, you can install `@types/eslint-config-prettier` package
  ],
  languageOptions: {
    parser: tseslint.parser,
    parserOptions: {
      sourceType: 'module',
      extraFileExtensions: ['.svelte']
    },
    globals: { ...globals.browser, ...globals.node },
  },
  rules: { /* rules for js/ts/svelte */ } // don't set 'svelte/*' rules here
});

const svelteConfig = tseslint.config({
  extends: [
    ...svelte.configs['flat/recommended'],
    ...svelte.configs['flat/prettier']
  ],
  languageOptions: {
    parser: svelteParser,
    parserOptions: { parser: tseslint.parser }
  },
  // ↓ rule types;  sveltejs/eslint-plugin-svelte #735 
  /** @type {import('eslint').Linter.RulesRecord} */
  rules: { /* rules for svelte */ }
});

/** @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} */
export default = [
  ...defaultConfig,
  ...svelteConfig,
  {
    // other override settings. e.g. for `files: ['**/*.test.*']`
  },
  { ignores: ['node_modules/', /* other ignores */] } // overrides global ignores
];

I found why my config file was not working with ts files in vscode, yet the command line interface worked as expected!

I needed this config line in vscode: "eslint.experimental.useFlatConfig": true

@MathiasWP : I had the same problem as you, but I got it working by adding files: ["**/*.svelte"], to the configuration object block that contains languageOptions.

My understanding, is that by adding files: ["**/*.svelte"], into this object, you tell it that this override will only apply to .svelte files.

    {
        files: ["**/*.svelte"],
        languageOptions: {
            ecmaVersion: 2022,
            sourceType: "module",
            parser: svelteParser,
            parserOptions: {
                parser: tsParser,
                extraFileExtensions: [".svelte"],
            },
        },
    },

I am not sure what the extraFileExtensions: [".svelte"], is supposed to do? It seem that I can remove it without any ill effect. Hope this helps.

Thank you so much! This solved the issue for me! 🎉