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 · 18 comments
Open
2 tasks done

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

MathiasWP opened this issue Apr 8, 2024 · 18 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

@hanssonduck
Copy link

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

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

10 participants