Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linting with flat config (ESLint 9) does not work with TypeScript #732

Open
2 tasks done
MathiasWP opened this issue Apr 8, 2024 · 29 comments
Open
2 tasks done

Linting with flat config (ESLint 9) does not work with TypeScript #732

MathiasWP opened this issue Apr 8, 2024 · 29 comments

Comments

@MathiasWP
Copy link

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

@MathiasWP MathiasWP changed the title Linting fails on TypeScript project Linting fails on Eslint 9 + TypeScript + Svelte project Apr 8, 2024
@MathiasWP MathiasWP changed the title Linting fails on Eslint 9 + TypeScript + Svelte project Linting with flat config does not work with TypeScript project Apr 8, 2024
@MathiasWP MathiasWP changed the title Linting with flat config does not work with TypeScript project Linting with flat config (eslint 9) does not work with TypeScript project Apr 8, 2024
@MathiasWP MathiasWP changed the title Linting with flat config (eslint 9) does not work with TypeScript project Linting with flat config (ESLint 9) does not work with TypeScript project Apr 8, 2024
@MathiasWP MathiasWP changed the title Linting with flat config (ESLint 9) does not work with TypeScript project Linting with flat config (ESLint 9) does not work on TypeScript project Apr 8, 2024
@MathiasWP MathiasWP changed the title Linting with flat config (ESLint 9) does not work on TypeScript project Linting with flat config (ESLint 9) does not work with TypeScript Apr 8, 2024
@GauBen
Copy link

GauBen commented Apr 8, 2024

Hey, I had it working with the following config:

import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginSvelte from "eslint-plugin-svelte";
import globals from "globals";
import svelteParser from "svelte-eslint-parser";
import tsEslint from "typescript-eslint";

export default tsEslint.config(
  js.configs.recommended,
  ...tsEslint.configs.recommended,
  ...eslintPluginSvelte.configs["flat/recommended"],
  eslintConfigPrettier,
  ...eslintPluginSvelte.configs["flat/prettier"],
  {
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: "module",
      globals: { ...globals.node, ...globals.browser },
      parser: svelteParser,
      parserOptions: {
        parser: tsEslint.parser,
        extraFileExtensions: [".svelte"],
      },
    },
  },
  {
    ignores: [
      "**/.svelte-kit",
      "**/.vercel",
      "**/.yarn",
      "**/build",
      "**/node_modules",
      "**/package",
    ],
  },
);

Hope it helps

Edit: updated with better imports and ignores

@Scc33
Copy link

Scc33 commented Apr 9, 2024

Also working for me. According to the rollout tracker there is support.

import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginSvelte from "eslint-plugin-svelte";
import globals from "globals";
import tsEslint from "typescript-eslint";
import vitest from "eslint-plugin-vitest";
import playwright from "eslint-plugin-playwright";

export default [
  js.configs.recommended,
  ...tsEslint.configs.recommended,
  ...eslintPluginSvelte.configs["flat/recommended"],
  eslintConfigPrettier,
  {
    ...playwright.configs["flat/playwright"],
    files: ["tests/**"]
  },
  vitest.configs.recommended,
  ...eslintPluginSvelte.configs["flat/prettier"],
  {
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: { ...globals.node, ...globals.browser },
      parserOptions: {
        extraFileExtensions: [".svelte"]
      }
    }
  },
  {
    ignores: [
      ".svelte-kit",
      "build",
      "package",
      "coverage",
      "node_modules",
      "playwright.config.js"
    ]
  }
];

@MathiasWP
Copy link
Author

Hey, I had it working with the following config:

import js from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginSvelte from "eslint-plugin-svelte";
import globals from "globals";
import svelteParser from "svelte-eslint-parser";
import tsEslint from "typescript-eslint";

export default tsEslint.config(
  js.configs.recommended,
  ...tsEslint.configs.recommended,
  ...eslintPluginSvelte.configs["flat/recommended"],
  eslintConfigPrettier,
  ...eslintPluginSvelte.configs["flat/prettier"],
  {
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: "module",
      globals: { ...globals.node, ...globals.browser },
      parser: svelteParser,
      parserOptions: {
        parser: tsParser,
        extraFileExtensions: [".svelte"],
      },
    },
  },
  { ignores: [".svelte-kit", "build", "package"] },
);

Hope it helps

Thank you for helping out, i really appreciate it! This config works for my Svelte files it seems like, but in almost all my TypeScript files i get a 5:14 error Parsing error: Unexpected token { error (is usually crashes at imports like import { type Foo } from 'bar'.

@MathiasWP
Copy link
Author

Also working for me. According to the rollout tracker there is support.

import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginSvelte from "eslint-plugin-svelte";
import globals from "globals";
import tsEslint from "typescript-eslint";
import vitest from "eslint-plugin-vitest";
import playwright from "eslint-plugin-playwright";

export default [
  js.configs.recommended,
  ...tsEslint.configs.recommended,
  ...eslintPluginSvelte.configs["flat/recommended"],
  eslintConfigPrettier,
  {
    ...playwright.configs["flat/playwright"],
    files: ["tests/**"]
  },
  vitest.configs.recommended,
  ...eslintPluginSvelte.configs["flat/prettier"],
  {
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: { ...globals.node, ...globals.browser },
      parserOptions: {
        extraFileExtensions: [".svelte"]
      }
    }
  },
  {
    ignores: [
      ".svelte-kit",
      "build",
      "package",
      "coverage",
      "node_modules",
      "playwright.config.js"
    ]
  }
];

Thank you for helping out, i appreciate it! This config works fine for me on TypeScript files, but it does not work on Svelte files. The funny thing is that i tried to merge your and @GauBen's configs (because the one works with TS and the other with Svelte), but with no luck. :/

@robinengler
Copy link

robinengler commented Apr 9, 2024

@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.

@MathiasWP
Copy link
Author

@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! 🎉

@robinengler
Copy link

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',
    },
  },
];

@GauBen
Copy link

GauBen commented Apr 10, 2024

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
Copy link
Author

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',
    },
  },
];

I agree, having an example would help a lot

@kansson
Copy link

kansson commented Apr 11, 2024

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.

@MathiasWP
Copy link
Author

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

Thanks, feels nice to remove @typescript-eslint/parser

@avi12
Copy link

avi12 commented Apr 12, 2024

I managed to lint both TypeScript *.svelte and *.ts:

import eslint from "@eslint/js";
import pluginImport from "eslint-plugin-import";
import svelteEslint from "eslint-plugin-svelte";
import globals from "globals";
import svelteParser from "svelte-eslint-parser";
import tsEslint from "typescript-eslint";

export default [
  eslint.configs.recommended,
  ...tsEslint.configs.recommended,
  ...svelteEslint.configs["flat/recommended"],
  {
    files: ["**/*.svelte"],
    languageOptions: {
      parser: svelteParser,
      parserOptions: {
        parser: tsEslint.parser
      },
      globals: {
        ...globals.browser
      }
    }
  },
  {
    files: ["**/*.ts"],
    languageOptions: {
      parser: tsEslint.parser
    }
  },
  {
    plugins: {
      "@typescript-eslint": tsEslint.plugin,
      import: pluginImport
    },
    rules: {
      semi: "warn",
      "svelte/sort-attributes": "warn"
    }
  }
];

@szinn
Copy link

szinn commented Apr 13, 2024

Thanks for all the config hints - I'm new to svelte and eslint 9 is complaining with this config about eslint.config.cjs

> eslint .

(node:19898) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)

Oops! Something went wrong! :(

ESLint: 9.0.0

/Users/scotte/Development/Projects/wordacle/eslint.config.cjs:1
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
^^^^^^

SyntaxError: Cannot use import statement outside a module

Running it with npx eslint .

@avi12
Copy link

avi12 commented Apr 13, 2024

@szinn Change the filename to eslint.config.js

@szinn
Copy link

szinn commented Apr 13, 2024

Ah, I also reinstalled modules - prettier config caused grief as well

@pboling
Copy link

pboling commented Apr 15, 2024

This is wonderful! I was able to use and extend the above into a config that supports custom configs for my test files, with the addition of the markdown linter, and the jsdoc linter. Hopefully this helps others build even better ones!

See my eslint.config.js
import eslint from "@eslint/js";
import pluginImport from "eslint-plugin-import";
import svelteEslint from "eslint-plugin-svelte";
import globals from "globals";
import svelteParser from "svelte-eslint-parser";
import tsEslint from "typescript-eslint";
import jsdoc from 'eslint-plugin-jsdoc';
import markdown from 'eslint-plugin-markdown';

const testingDSL = {
	it: "readonly",
	expect: "readonly",
	describe: "readonly",
};

const ignores = [
	// Sure, let's lint our lint config... :D
	// ./eslint.config.js
	'.DS_Store',
	'.env',
	'.env.*',
	'.github',
	// On CI our PNPM store is local to the application source
	'.pnpm-store/**/*',
	'.svelte-kit/**/*',
	'.vscode',
	'node_modules/**/*',
	'build/**/*',
	'package/**/*',

	// Ignore files for PNPM, NPM and YARN
	'pnpm-lock.yaml',
	'package-lock.json',
	'yarn.lock',

	// i18n dictionaries and auto-generated data
	'src/paraglide/**/*'
];

/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
	{ ignores },
	...markdown.configs.recommended,
	eslint.configs.recommended,
	...tsEslint.configs.recommended,
	...svelteEslint.configs["flat/prettier"],
	jsdoc.configs['flat/recommended'],
	{
		files: ["**/*.svelte"],
		languageOptions: {
			parser: svelteParser,
			parserOptions: {
				parser: tsEslint.parser
			},
			globals: {
				...globals.browser
			}
		}
	},
	{
		files: ["**/*.svelte.test.ts"],
		languageOptions: {
			parser: svelteParser,
			parserOptions: {
				parser: tsEslint.parser
			},
			globals: {
				...globals.browser,
				...testingDSL
			},
		},
	},
	{
		files: ["**/*.ts"],
		languageOptions: {
			parser: tsEslint.parser
		}
	},
	{
		files: ["**/*.test.ts"],
		languageOptions: {
			parser: tsEslint.parser,
			globals: {
				...testingDSL
			},
		},
	},
	{
		files: ["**/*server.ts"],
		languageOptions: {
			parser: tsEslint.parser,
			globals: {
				...globals.node
			}
		}
	},
	{
		files: ["**/*server.test.ts"],
		languageOptions: {
			parser: tsEslint.parser,
			globals: {
				...globals.node,
				...testingDSL
			}
		}
	},
	{
		plugins: {
			"@typescript-eslint": tsEslint.plugin,
			import: pluginImport
		},
		rules: {
			semi: "warn",
			"svelte/sort-attributes": "warn"
		}
	}
];

@Masstronaut
Copy link

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.

@robinengler I wanted to answer this Q because it's important to understand why thigns are done!

The ts parser will ignore files with file extensions it doesn't expect (such as .svelte). The svelte parser is passed a ts parser instance to handle parsing typescript inside svelte files (sorry if that's a confusing explanation). To make sure the ts parser will run on this code, we pass the extraFileExtensions parameter to the svelte parser, which in turn passes it to the ts parser to ensure it parses the typescript in svelte files for us. If you don't, the ts parser will report the following error for every svelte file:

The extension for the file (`.svelte`) is non-standard. You should add `parserOptions.extraFileExtensions` to your config

If you weren't getting any errors from removing this line, it could be that your config was not actually linting the .svelte files in your project. Hope this helps!

@u-sho
Copy link

u-sho commented Apr 30, 2024

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
];

@kkirkfield
Copy link

I haven't been able to get any of the above configs working with eslint 9.3.0 and typescript-eslint 8.0.0-alpha.20 for .svelte files but it does work for all .js and .ts files.

From typescript-eslint/typescript-eslint#8211:

Note that we do not plan on backporting ESLint v9 support to v7 versions of typescript-eslint. There are a lot of breaking changes inherent to our ESLint v9 support.

The error I'm seeing on .svelte files is:
0:0 error Parsing error: "parserOptions.programs" has been provided for @typescript-eslint/parser. The file was not found in any of the provided program instance(s): src\routes\+page.svelte

Here is an example config that is getting this error.

eslint.config.js

import js from '@eslint/js';
import ts from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';

const tsConfig = ts.config({
	files: ['**/*.js', '**/*.ts', '**/*.svelte'],
	extends: [
		js.configs.recommended,
		...ts.configs.strictTypeChecked,
		...ts.configs.stylisticTypeChecked,
		prettier
	],
	languageOptions: {
		globals: {
			...globals.browser,
			...globals.nodeBuiltin
		},
		parserOptions: {
			project: './tsconfig.eslint.json',
			tsconfigRootDir: import.meta.dirname,
			extraFileExtensions: ['.svelte']
		}
	}
});

const svelteConfig = ts.config({
	files: ['**/*.svelte'],
	extends: [...svelte.configs['flat/recommended'], ...svelte.configs['flat/prettier']],
	languageOptions: {
		parserOptions: {
			parser: ts.parser
		}
	}
});

export default ts.config(...tsConfig, ...svelteConfig, {
	ignores: ['.svelte-kit/']
});

tsconfig.eslint.json

{
	"extends": "./tsconfig.json",
	"files": ["eslint.config.js", "svelte.config.js", "vite.config.ts"]
}

tsconfig.json

{
	"extends": "./.svelte-kit/tsconfig.json",
	"compilerOptions": {
		"allowJs": true,
		"checkJs": true,
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"resolveJsonModule": true,
		"skipLibCheck": true,
		"sourceMap": true,
		"strict": true,
		"moduleResolution": "bundler"
	}
}

@0-BSCode
Copy link

0-BSCode commented Jun 1, 2024

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',
    },
  },
];

Thanks for this! I was wondering if we could see the package.json for this to look at the package versions. I'm running into the same issue as @kkirkfield and I think seeing the versions of the package used would help me debug the issue.

@pboling
Copy link

pboling commented Jun 1, 2024

Did you look at the one I posted above (hidden in a details tag)? It continues to work, and I've upgraded since I posted to every single more recent release.

The primary difference I see is I am not using the pre-release alpha, but the stable release:

		"@typescript-eslint/eslint-plugin": "^7.11.0",

@pboling
Copy link

pboling commented Jun 1, 2024

Here is the relevant section of my package.json:

	"devDependencies": {
		"@eslint/js": "^9.3.0",
		"@floating-ui/dom": "^1.6.5",
		"@flydotio/dockerfile": "^0.5.7",
		"@fontsource/fira-mono": "^5.0.13",
		"@inlang/cli": "^2.18.0",
		"@inlang/paraglide-js": "^1.9.1",
		"@inlang/paraglide-sveltekit": "^0.8.6",
		"@internationalized/date": "^3.5.4",
		"@mockoon/cli": "^8.1.1",
		"@playwright/test": "^1.44.1",
		"@skeletonlabs/skeleton": "^2.10.0",
		"@skeletonlabs/tw-plugin": "^0.4.0",
		"@sveltejs/adapter-node": "^5.0.1",
		"@sveltejs/kit": "^2.5.10",
		"@sveltejs/vite-plugin-svelte": "^3.1.0",
		"@tailwindcss/forms": "^0.5.7",
		"@testing-library/jest-dom": "^6.4.5",
		"@testing-library/svelte": "^5.1.0",
		"@testing-library/user-event": "^14.5.2",
		"@types/d3": "^7.4.3",
		"@types/eslint": "^8.56.10",
		"@types/node": "^20.12.12",
		"@typescript-eslint/eslint-plugin": "^7.11.0",
		"@vitest/ui": "^1.6.0",
		"autoprefixer": "^10.4.19",
		"bits-ui": "^0.21.10",
		"browser-tab-id": "^0.0.8",
		"browserslist": "^4.23.0",
		"cssnano": "^7.0.1",
		"dotenv": "^16.4.5",
		"drizzle-kit": "^0.21.4",
		"eslint": "^9.3.0",
		"eslint-config-prettier": "^9.1.0",
		"eslint-plugin-import": "^2.29.1",
		"eslint-plugin-jsdoc": "^48.2.6",
		"eslint-plugin-markdown": "^5.0.0",
		"eslint-plugin-prettier": "^5.1.3",
		"eslint-plugin-svelte": "^2.39.0",
		"formsnap": "^1.0.0",
		"globals": "^15.3.0",
		"jsdom": "^24.1.0",
		"pgtools": "^1.0.1",
		"postcss": "^8.4.38",
		"postcss-cli": "^11.0.0",
		"postcss-import": "^16.1.0",
		"postcss-nesting": "^12.1.5",
		"postcss-preset-env": "^9.5.14",
		"postcss-simple-vars": "^7.0.1",
		"prettier": "^3.2.5",
		"prettier-plugin-svelte": "^3.2.3",
		"prettier-plugin-tailwindcss": "^0.5.14",
		"stylelint": "^16.6.1",
		"stylelint-config-standard": "^36.0.0",
		"stylelint-order": "^6.0.4",
		"svelte": "5.0.0-next.143",
		"svelte-check": "^3.7.1",
		"svelte-eslint-parser": "^0.36.0",
		"svelte2tsx": "^0.7.8",
		"sveltekit-rate-limiter": "^0.5.1",
		"sveltekit-superforms": "^2.14.0",
		"tailwindcss": "^3.4.3",
		"tslib": "^2.6.2",
		"tsx": "^4.11.0",
		"typescript": "^5.4.5",
		"typescript-eslint": "^7.11.0",
		"vite": "^5.2.12",
		"vitest": "^1.6.0"
	},
	"type": "module",
	"dependencies": {
		"@honeycombio/opentelemetry-node": "^0.7.2",
		"@inlang/language-tag": "^1.5.1",
		"@kripod/uuidv7": "^0.3.4",
		"@lucia-auth/adapter-drizzle": "^1.0.7",
		"@opentelemetry/api": "^1.8.0",
		"@opentelemetry/auto-instrumentations-node": "^0.46.1",
		"@opentelemetry/context-zone": "^1.24.1",
		"@opentelemetry/exporter-trace-otlp-http": "^0.51.1",
		"@opentelemetry/instrumentation": "^0.51.1",
		"@opentelemetry/instrumentation-document-load": "^0.38.0",
		"@opentelemetry/instrumentation-fetch": "^0.51.1",
		"@opentelemetry/instrumentation-long-task": "^0.38.0",
		"@opentelemetry/instrumentation-user-interaction": "^0.38.0",
		"@opentelemetry/instrumentation-xml-http-request": "^0.51.1",
		"@opentelemetry/resources": "^1.24.1",
		"@opentelemetry/sdk-node": "^0.51.1",
		"@opentelemetry/sdk-trace-web": "^1.24.1",
		"@opentelemetry/semantic-conventions": "^1.24.1",
		"@sendgrid/mail": "^8.1.3",
		"@sentry/sveltekit": "^8.5.0",
		"arctic": "^1.9.0",
		"connect-web-sdk": "^1.1.0",
		"d3": "^7.9.0",
		"drizzle-orm": "^0.30.10",
		"drizzle-zod": "^0.5.1",
		"lucia": "^3.2.0",
		"lucide-svelte": "^0.379.0",
		"oslo": "^1.2.0",
		"postgres": "^3.4.4",
		"redis": "^4.6.14",
		"svelte-idb-store": "^0.2.0",
		"svelte-persisted-store": "^0.9.4",
		"svelte-ux": "^0.64.1",
		"zod": "^3.23.8"
	},
	"engines": {
		"node": "^20.13.1",
		"npm": "^10.5.0",
		"pnpm": "^9.1.3"
	}

@0-BSCode
Copy link

0-BSCode commented Jun 2, 2024

Did you look at the one I posted above (hidden in a details tag)? It continues to work, and I've upgraded since I posted to every single more recent release.

The primary difference I see is I am not using the pre-release alpha, but the stable release:

		"@typescript-eslint/eslint-plugin": "^7.11.0",

When I try to install this package via npm install -D @typescript-eslint/eslint-plugin, I get the following error:

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/eslint
npm ERR!   dev eslint@"^9.4.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer eslint@"^8.56.0" from @typescript-eslint/[email protected]
npm ERR! node_modules/@typescript-eslint/parser
npm ERR!   peer @typescript-eslint/parser@"^7.0.0" from @typescript-eslint/[email protected]      
npm ERR!   node_modules/@typescript-eslint/eslint-plugin
npm ERR!     dev @typescript-eslint/eslint-plugin@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

I run into a similar error when trying to install typescript-eslint via npm i -D typescript-eslint:

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/eslint
npm ERR!   dev eslint@"^9.4.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer eslint@"^8.56.0" from [email protected]
npm ERR! node_modules/typescript-eslint
npm ERR!   dev typescript-eslint@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

My understanding is both these packages (typescript-eslint and @typescript-eslint/eslint-plugin require version 8 of eslint, but I'm using version 9. The part where I get confused is how to get around this since, in your package.json, it seems that you were able to get these packages installed while using eslint version 9. Attached below is my package.json devDependencies for extra context.

  "devDependencies": {
    "@sveltejs/vite-plugin-svelte": "^3.0.2",
    "@tsconfig/svelte": "^5.0.2",
    "autoprefixer": "^10.4.19",
    "eslint": "^9.4.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-prettier": "^5.1.3",
    "eslint-plugin-svelte": "^2.39.0",
    "husky": "^9.0.11",
    "lint-staged": "^15.2.5",
    "postcss": "^8.4.38",
    "prettier": "^3.2.5",
    "prettier-plugin-svelte": "^3.2.3",
    "prettier-plugin-tailwindcss": "^0.6.0",
    "svelte": "^4.2.17",
    "svelte-check": "^3.6.7",
    "tailwindcss": "^3.4.3",
    "tslib": "^2.6.2",
    "typescript": "^5.2.2",
    "vite": "^5.2.0"
  },

@ota-meshi
Copy link
Member

Thanks for all your comments! I still don't know the best practices for integrating with typescript-eslint, but using typescript-eslint@v8-beta might work.
https://x.com/tseslint/status/1795205941647884611

@falco467
Copy link

falco467 commented Jun 3, 2024

While the provided configs work for the simple recommended ruleset I cannot get it to work with the actual type-checking configs of typescript-eslint. To enable actual type-parsing, we need to enable the project: true parser option. And as soon as that is enabled, the error reads:

Parsing error: "parserOptions.programs" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided program instance(s): src\drehscheibe.svelte

import epSvelte from 'eslint-plugin-svelte'
import svelteParser from 'svelte-eslint-parser'
import tsEslint from 'typescript-eslint'

export default [
  ...tsEslint.configs.strictTypeChecked,
  ...epSvelte.configs['flat/recommended'],
  {
    languageOptions: {
      parser: tsEslint.parser,
      parserOptions: {
        project: true, // This line breaks the config (even with the other configs) - but it is needed for type-checking.
      },
    },
  },
  {
    files: ['**/*.svelte'],
    languageOptions: {
      parser: svelteParser,
      parserOptions: {
        parser: tsEslint.parser,
      }
    },
  },
]

And the inclusion of extra-file extensions doesn't make a difference. Also including the project: true in both parserOptions doesn't change anything. JS/TS Files get handled correctly, but svelte files are not recognized as soon as "project" is set to anything.

@falco467
Copy link

falco467 commented Jun 3, 2024

@kkirkfield It seems to work when I add programs: false to the parserOptions of the typescript-eslint block.

@kkirkfield
Copy link

@falco467

@kkirkfield It seems to work when I add programs: false to the parserOptions of the typescript-eslint block.

programs is set to undefined by default, and gets computed from parserOptions.project. When you are setting programs to another value it is probably not running any programs for the typescript-eslint parser and is not doing what you expect. I'll try and give it a shot today and let you know what actually happens for me.

https://typescript-eslint.io/packages/parser#programs

To clarify some of the other confusion above...

project and tsconfigRootDir

If you want to use the strict type checked rules from typescript-eslint then you need to set project and tsconfigRootDir. If you just want to run the rules on src files then you can use project: true and tsconfigRootDir: import.meta.dirname. If you want to also parse your config files, then you can use project: './tsconfig.eslint.json' and set additional files in the files array in tsconfig.eslint.json. See my example code above if you need.

extraFileExtensions

For this setting you need to include the .svelte file extension or the typescript-eslint parser will not run for code blocks in svelte files. There is a gotcha with this setting. It needs to be in a parser options group that applies to all files you would run the typescript-eslint parser for. The reason for this is because the computed parser options and the parser itself is a singleton. The parser options are only computed for the first file that eslint happens to run on. Setting extraFileExtensions or any other parser options in a separate group that only applies to a subset of files will be ignored (or only the first group to run will be used, and others will be ignored). typescript-eslint parser is implemented this way for performance reasons because it would be much slower to have to recreate the parser for every program. See my example code above that shows setting this config in a group that applies to all js, ts, and svelte files.

Here is a link to an issue showing how this is working as intended.
typescript-eslint/typescript-eslint#6778

typescript-eslint v7

There is no plan to officially backport support for eslint v9 to typescript-eslint v7. Support for eslint v9 is only officially supported in typescript-eslint v8 beta. I linked to this above in an early comment but I'm including it here as well.

From typescript-eslint/typescript-eslint#8211:

Note that we do not plan on backporting ESLint v9 support to v7 versions of typescript-eslint. There are a lot of breaking changes inherent to our ESLint v9 support.

@falco467
Copy link

falco467 commented Jun 4, 2024

@kkirkfield

  1. I'm using typescript-eslint v8-alpha
  2. I was under the impression that settings programs to some value will in fact parse/lint all code delivered by that program and ignore the extraFileExtensions. I assumed they are only needed when programs is null/false.
  3. Setting programs to false seems to work for me - .svelte files are parsed and typescript rule violations seem to be reported on first glance. But maybe it is only working superficially - I'm looking forward to your report on this.

@ZerdoX-x
Copy link

ZerdoX-x commented Jun 9, 2024

I could get it working initially and I didn't even know it was an issue for someone. Below is my config (yeah I know it's messy, lmk if I can make it better)

eslint.config.js
// @ts-check

import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import globals from "globals";
import tsConfigForConfigFiles from "./tsconfig.configs.json" assert { type: "json" };
/* plugins */
const { plugin: tsPlugin } = tseslint;
import sveltePlugin from "eslint-plugin-svelte";
/* parsers */
const { parser: tsParser } = tseslint;
import svelteParser from "svelte-eslint-parser";
import * as tsExtraFilesParser from "typescript-eslint-parser-for-extra-files";

/* rules for different languages */
const RULES = {
	ts: {
		"@typescript-eslint/no-unused-vars": [
			"error",
			{
				argsIgnorePattern: "^_",
				varsIgnorePattern: "^_",
				caughtErrorsIgnorePattern: "^_",
				destructuredArrayIgnorePattern: "^_",
			},
		],
	},
};

export default tseslint.config(
	/* recommended configs */
	{
		languageOptions: {
			parserOptions: {
				project: "./tsconfig.json",
				ecmaVersion: "latest",
			},
		},
	},
	eslint.configs.recommended,
	...tseslint.configs.recommended,
	...tseslint.configs.recommendedTypeChecked,
	// @ts-expect-error not sure why yet. probably due to lack of eslint 9 support
	...sveltePlugin.configs["flat/recommended"],
	/* basic js include files */
	{
		files: ["**/*.js", "**/*.cjs", "**/*.mjs"],
	},
	/* ignore on all levels */
	{
		ignores: [
			"build",
			"node_modules",
			".svelte-kit",
			".vercel",
			// because of import assertion of tsconfig.json
			"eslint.config.js",
		],
	},
	/* define globals for nodejs files */
	{
		files: ["**/*.cjs", "**/*.cts"],
		languageOptions: {
			globals: {
				...globals.node,
			},
		},
	},
	/* define globals for browser files */
	{
		files: ["**/*.js", "**/*.mjs", "**/*.ts", "**/*.mts", "**/*.svelte"],
		languageOptions: {
			globals: {
				...globals.browser,
			},
		},
	},
	/* configure *.config.ts files in root (tools configs) */
	{
		files: tsConfigForConfigFiles.include,
		plugins: {
			"@typescript-eslint": tsPlugin,
		},
		languageOptions: {
			parser: tsParser,
			parserOptions: {
				sourceType: "module",
				project: "./tsconfig.configs.json",
			},
		},
		rules: RULES.ts,
	},
	/* configure typescript */
	{
		files: ["**/*.ts", "**/*.cts", "**/*.mts"],
		ignores: tsConfigForConfigFiles.include,
		plugins: {
			"@typescript-eslint": tsPlugin,
		},
		languageOptions: {
			parser: tsExtraFilesParser,
			parserOptions: {
				sourceType: "module",
				extraFileExtensions: [".svelte"],
			},
		},
		rules: RULES.ts,
	},
	/* configure svelte */
	{
		plugins: {
			svelte: sveltePlugin,
			"@typescript-eslint": tsPlugin,
		},
		files: ["**/*.svelte"],
		languageOptions: {
			parser: svelteParser,
			parserOptions: {
				parser: tsExtraFilesParser,
				svelteFeatures: {
					experimentalGenerics: true,
				},
				extraFileExtensions: [".svelte"],
			},
		},
		rules: {
			...RULES.ts,
			...sveltePlugin.configs.recommended.rules,
		},
	},
);

I guess the main reason I could get something working with strict type checking rules (@falco467 case) because I was using @ota-meshi typescript-eslint-parser-for-extra-files

But today I stumbled upon strange case which I've never seen before:

<script lang="ts">
export let authorization: string | undefined = undefined;

$: headers: {
	Authorization: authorization ? `test ${authorization}` : undefined,
	// Invalid type "never" of template literal expression. eslint (@typescript-eslint/restrict-template-expressions)
},
</script>

I am using all latest packages (literally, upgraded all just right before posting). [email protected]

@ota-meshi you got any ideas? could this be an issue for extra files parser?

upd: for a workaround i am currently using empty string instead of undefined:
export let authorization: string | undefined = '';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests