sveltejs / eslint-plugin-svelte

ESLint plugin for Svelte using AST
https://sveltejs.github.io/eslint-plugin-svelte/
MIT License
276 stars 30 forks source link

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

Open MathiasWP opened 2 months ago

MathiasWP commented 2 months ago

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

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

GauBen commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

@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 commented 2 months ago

@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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

@szinn Change the filename to eslint.config.js

szinn commented 2 months ago

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

pboling commented 2 months ago

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 ```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 commented 2 months ago

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 commented 2 months ago

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 ```js 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 commented 1 month ago

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 https://github.com/typescript-eslint/typescript-eslint/issues/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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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: palette-town@0.0.0
npm ERR! Found: eslint@9.4.0
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/parser@7.11.0
npm ERR! node_modules/@typescript-eslint/parser
npm ERR!   peer @typescript-eslint/parser@"^7.0.0" from @typescript-eslint/eslint-plugin@7.11.0      
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: palette-town@0.0.0
npm ERR! Found: eslint@9.4.0
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@7.11.0
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 commented 1 month ago

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 commented 4 weeks ago

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 commented 4 weeks ago

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

kkirkfield commented 4 weeks ago

@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. https://github.com/typescript-eslint/typescript-eslint/issues/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 https://github.com/typescript-eslint/typescript-eslint/issues/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 commented 4 weeks ago

@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 commented 3 weeks ago

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). typescript-eslint@8.0.0-alpha.29

@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 = '';

kkirkfield commented 2 weeks ago

@falco467 Thanks for the suggestion! Setting programs to false seems to fix the issue I was running into. I tested various rules in .js, .ts, and .svelte files and all rules are working, including the type checked rules.

eslint.config.js ```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'], programs: false } } }); 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/'] }); ```
RajeshPandey057 commented 12 hours ago

This seems to be working nicely for me along with type safety inside eslint.config.js

// @ts-check
import js from '@eslint/js';
import playwright from 'eslint-plugin-playwright';
import prettier from 'eslint-plugin-prettier/recommended';
import svelte from 'eslint-plugin-svelte';
import vitest from 'eslint-plugin-vitest';
import globals from 'globals';
import svelteParser from 'svelte-eslint-parser';
import ts from 'typescript-eslint';

/** @type {import('typescript-eslint').Config} */
export default ts.config(
    js.configs.recommended,
    ...ts.configs.recommendedTypeChecked,
    ...svelte.configs['flat/recommended'],
    prettier,

    {
        ...playwright.configs['flat/recommended'],
        files: ['tests/**'], // or any other pattern
        plugins: {
            vitest
        },
        rules: {
            ...vitest.configs.recommended.rules
        },
        settings: {
            vitest: {
                typecheck: true
            }
        },
        languageOptions: {
            globals: {
                ...vitest.environments.env.globals
            }
        }
    },
    {
        languageOptions: {
            ecmaVersion: 'latest',
            sourceType: 'module',
            globals: { ...globals.node, ...globals.browser },
            parserOptions: {
                project: true,
                parser: ts.parser,
                extraFileExtensions: ['.svelte'],
                tsconfigRootDir: import.meta.dirname
            }
        }
    },
    {
        files: ['**/*.svelte', '*.svelte'],
        languageOptions: {
            ecmaVersion: 'latest',
            sourceType: 'module',
            globals: { ...globals.browser },
            parser: svelteParser,
            parserOptions: {
                parser: ts.parser,
                extraFileExtensions: ['.svelte']
            }
        },
        rules: {
            'import/no-named-as-default': 'off',
            'import/no-named-as-default-member': 'off',
            'svelte/no-target-blank': 'error',
            'svelte/no-at-debug-tags': 'error',
            'svelte/no-reactive-functions': 'error',
            'svelte/no-reactive-literals': 'error'
        }
    },
    {
        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'
        }
    },
    {
        files: ['**/*.config.*'],
        extends: [ts.configs.disableTypeChecked]
    },
    {
        ignores: [
            'coverage',
            '**/build',
            '**/.husky',
            '**/package',
            '**/.svelte-kit',
            '**/docker-data',
            '**/node_modules',
            'src/lib/i18n/*.ts',
            '.env',
            '.env.*',
            '!*.cjs',
            'yarn.lock',
            '.DS_Store',
            '!.env.example',
            'pnpm-lock.yaml',
            'package-lock.json'
        ]
    }
);