vscode-eslint: VSCode doesn't run ESLint Server with eslint.config.js flat config

VSCode is not running ESLint server on my codebase when using the new ESLint Flat Config eslint.config.js.

I have the following setup:

  • The app is a Vite + Svelte + TS starter
  • The issue happens in a monorepo (pnpm workspace)
  • ESLint correctly lint my codebase when invoked via the CLI pnpm eslint src
  • Packages has been reinstalled several times with pnpm install and the ESLint server has been restarted
  • VSCode doesn’t run ESLint server on my codebase when using the flat config

My codebase structure

/client
   /.vscode
      settings.json
   /src
      main.ts
      App.svelte
   eslint.config.js
   package.json
   vite.config.ts
package.json
pnpm-workspace.yaml

ESLint correctly lint my codebase with pnpm eslint src and find errors in both src/main.ts and src/App.svelte.

But VSCode is unable to run the ESLint server with eslint.config.js located at the workspace root

The Output console says

[Info  - 5:43:59 PM] ESLint server is starting.
[Info  - 5:44:00 PM] ESLint server running in node v16.14.2
[Info  - 5:44:00 PM] ESLint server is running.
[Info  - 5:44:00 PM] 
Failed to load the ESLint library for the document <project_root>/client/src/App.svelte

To use ESLint please install eslint by running pnpm install eslint in the workspace folder test
or globally using 'pnpm install -g eslint'. You need to reopen the workspace after installing eslint.

Alternatively you can disable ESLint for the workspace folder test by executing the 'Disable ESLint' command.

My settings.json located at client/.vscode/settings.json

{
  "css.lint.unknownAtRules": "ignore",
  "scss.lint.unknownAtRules": "ignore",
  "eslint.enable": true,
  "eslint.packageManager": "pnpm",
  "eslint.format.enable": true,
  "eslint.validate": [
    "javascript",
    "typescript",
    "svelte"
  ],
  "eslint.experimental.useFlatConfig": true,
  "eslint.workingDirectories": [{ "mode": "auto" }],
  "eslint.options": {
    "overrideConfigFile": "./eslint.config.js"
  },
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 26
  • Comments: 73 (28 by maintainers)

Most upvoted comments

Flat configs are still experimental and need to be enabled in VSCode ESLint using the eslint.experimental.useFlatConfig setting. I could not find such a setting in the repositories. Do you enable it?

Works for me

image

Please note that in ESLint extension you still need to enable flat config. The reason is that we need to call different API in ESLint and we need to find a way to abstract that. The setting is:

{
    "eslint.experimental.useFlatConfig": true
}

I just copied an old .eslintrc and downgraded to 8.0.0 and things are working. This team’s only job is to maintain this part of the software. Do better.

Still having issues with this @dbaeumer

[Error - 11:18:11 PM] Error: Could not find config file.
    at locateConfigFileToUse (/Users/x3r0/Development/amplify-testlab/test-ssr-1/node_modules/.pnpm/eslint@8.55.0/node_modules/eslint/lib/eslint/flat-eslint.js:332:21)
    at async calculateConfigArray (/Users/x3r0/Development/amplify-testlab/test-ssr-1/node_modules/.pnpm/eslint@8.55.0/node_modules/eslint/lib/eslint/flat-eslint.js:367:49)
    at async FlatESLint.lintText (/Users/x3r0/Development/amplify-testlab/test-ssr-1/node_modules/.pnpm/eslint@8.55.0/node_modules/eslint/lib/eslint/flat-eslint.js:941:25)
    at async /Users/x3r0/.vscode-insiders/extensions/dbaeumer.vscode-eslint-2.4.2/server/out/eslintServer.js:1:24852
    at async E (/Users/x3r0/.vscode-insiders/extensions/dbaeumer.vscode-eslint-2.4.2/server/out/eslintServer.js:1:19212)
    at async /Users/x3r0/.vscode-insiders/extensions/dbaeumer.vscode-eslint-2.4.2/server/out/eslintServer.js:1:221479
[Trace - 11:18:11 PM] Received notification 'eslint/status'.

The same problem 😢

I’m still having this issue on ESLint extension v2.4.2 with flat ESLint config and useFlatConfig set to true.

You’re looking for the people over at eslint who changed how the config is looked up. “This team” is as far as I can tell one person and you certainly aren’t paying them.

This is not experimental you morons, it is the default output for the configuration file with eslint --init now. The current behavior of simply not recognizing the new config, without showing any kind of warning, is unjustifiable.

The old config files are now deprecated and are unrecognized on latest:

https://eslint.org/docs/latest/use/configure/configuration-files-deprecated

I tested this with the vscode-eslint 3.0.3 alpha together with Yarn 4.1.1 in PnP mode using Eslint 8.57.0 and for me it seems like imports are not found when using a mjs config file but it works with a CommonJS config file.

With the mjs config I get an error like this one for whatever the first import happen to be in eslint.config.mjs:

[Error - 12:22:05 PM] An unexpected error occurred:
[Error - 12:22:05 PM] Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@eslint/js' imported from D:\Test\eslint.config.mjs
Did you mean to import @eslint-js-npm-8.57.0-00ead3710a-3c501ce8a9.zip/node_modules/@eslint/js/src/index.js?
    at new NodeError (node:internal/errors:405:5)
    at packageResolve (node:internal/modules/esm/resolve:893:9)
    at moduleResolve (node:internal/modules/esm/resolve:942:20)
    at defaultResolve (node:internal/modules/esm/resolve:1135:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:835:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36)

The mjs config file works fine when I run eslint from the cli though and I also ran yarn dlx @yarnpkg/sdks after updating eslint and the extension but it didn’t make any difference.

Like @Elxx said it does work if I change the config file to CommonJS instead so it seems like the problem lies in that it can’t handle config files that are ES modules?

The only eslint related settings I’ve set in .vscode\settings.json are these now that eslint.experimental.useFlatConfig isn’t needed any longer:

"eslint.nodePath": ".yarn/sdks",
"eslint.workingDirectories": [{ "mode": "auto" }],

RESOLVED - I’m leaving the comments below in case they help anyone in the future, but I think I’ve resolved this issue. After adding the following to my VSCode settings, it seems to be working correctly. Now to see if I can get it to work with the new flat config!

"eslint.workingDirectories": [
    { "pattern": "./apps/*/" },
    { "pattern": "./packages/*/" }
  ]

@dbaeumer - First of all, thank you so much for your help! I’ve been fighting with trying to get this working all week and it’s probably something really obvious that I’m missing, but I just can’t put my finger on it…

So first, the new folder structure:

- trinity (root folder)
  - apps
    - dealer
      - src
        - (...all project files)
      - package.json
      - tsconfig.json
      - .eslintrc.json
  - packages
    - tsconfig
      - base.json
      - package.json
    - eslint-config-custom
      - index.js
      - package.json
package.json
pnpm-workspace.yaml

A little bit of additional info - everything was working correctly before moving to a pnpm workspaces monorepo, both in the CLI and UI layers. To try and simplify the issue, I reverted back to the old eslint config and get that working first (as I’m now having issues with getting that to even work in the UI).

Here’s all the relevant config files:

– ROOT CONFIG FILES –

package.json

{
  "name": "trinity-frontend",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dealer": "pnpm --filter @trinity/dealer",
    "clean": "find ./ -name node_modules -type d -exec rm -rf {} +"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "prettier": "^2.8.4",
    "typescript": "^4.9.5"
  }
}

pnpm-workspace.yaml

packages:
- apps/*
- packages/*

– TYPESCRIPT CONFIG FILES –

/packages/tsconfig/package.json

{
  "name": "@trinity/tsconfig"
}

/packages/tsconfig/base.json

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true, 
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "ESNext", 
    "moduleResolution": "Node", 
    "resolveJsonModule": true,
    "isolatedModules": true, 
    "noEmit": true, 
    "jsx": "react-jsx", 
    "outDir": "dist", 
    "incremental": true, 
    "noImplicitAny": true,
    "sourceMap": true,
    "importHelpers": true, 
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true
  },
  "exclude": ["node_modules"]
}

– ESLINT CONFIG FILES –

/packages/eslint-config-custom/package.json

{
  "name": "eslint-config-custom",
  "main": "index.js",
  "version": "1.0.0",
  "dependencies": {
    "@typescript-eslint/eslint-plugin": "^5.53.0",
    "@typescript-eslint/parser": "^5.53.0",
    "eslint": "^8.35.0",
    "eslint-config-prettier": "^8.6.0",
    "eslint-import-resolver-typescript": "^3.5.3",
    "eslint-plugin-import": "^2.27.5",
    "eslint-plugin-jsx-a11y": "^6.7.1",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-simple-import-sort": "^10.0.0"
  }
}
/packages/eslint-config-custom/index.js

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 'latest',
    sourceType: 'module',
    project: './tsconfig.json',
  },
  extends: [
    'eslint:recommended',
    'plugin:jsx-a11y/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:react/jsx-runtime',
    'plugin:import/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  plugins: ['react', 'prettier', 'import', 'jsx-a11y', '@typescript-eslint', 'simple-import-sort'],
  settings: {
    react: {
      version: 'detect',
    },
    'import/resolver': {
      typescript: {
        paths: './tsconfig.json',
        alwaysTryTypes: true,
      },
    },
  },
  globals: {
    React: true,
    JSX: true,
    __APP_VERSION__: true,
  },
  rules: {REMOVED FOR BREVITY},
}

– APP CONFIG FILES –

/apps/dealer/package.json

{
  "name": "@trinity/dealer",
  "version": "3.2.7",
  "main": "index.tsx",
  "private": true,
  "dependencies": { REMOVED FOR BREVITY  },
  "devDependencies": {
    "@trinity/tsconfig": "workspace:^",
    "eslint-config-custom": "workspace:^",
  },
}

/apps/dealer/tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "extends": "@trinity/tsconfig/base.json",
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

/apps/dealer/.eslintrc.json

{
  "root": true,
  "extends": ["custom"]
}

So now what’s happening is there seems to be a discrepancy between the CLI and UI on where the tsconfig.json file lives… with the config above, the CLI tool seems to lint the project correctly, however the UI is looking for a tsconfig file in the root directory. So when I update the eslint config file to point to apps/dealer/tsconfig.json then the UI will lint, but the CLI stops working.

Also, I’m not sure if I’m better off trying to keep the config in separate packages the way I have it above or going back and just keeping all of the config in the root directory? While I only have a single app at the moment, I’m trying to prepare the repo to add several more apps and packages and I want to keep it as simple as possible to share both eslint and typescript across the entire project. Any help you could offer would be so appreciated! Thank you!

I created https://github.com/yarnpkg/berry/issues/6219 to track the mjs load failure in yarn.

"eslint.experimental.useFlatConfig": true, This works for me but it seems to disable legacy config files. Would be nice if I could have both options on but I guess that’s not possible?

Had a similar problem, adding rules:

	"eslint.experimental.useFlatConfig": true,
	"eslint.workingDirectories": [
		"folder/"
	],

solved the issue

It does recognize it (you moron) as long as you set your working path correctly.

I thought that this loader was injected in the node runtime by the vscode eslint sdk package that gets installed when you run the yarn dlx @yarnpkg/sdks vscode command so that it should be able to use ESM?

Yes, and this is what is happening. By setting $env:NODE_PATH=".yarn/sdks" node is instructed to load node modules from .yarn/sdks which should inject the loader according to: https://yarnpkg.com/getting-started/editor-sdks

The yarn dlx @yarnpkg/sdks vscode sets the eslint.nodePath setting (see https://github.com/davidkarlsson/vscode-eslint-test/blob/main/.vscode/settings.json#L6) which the extension then passes on to node.

The extension itself doesn’t know anything about yarn pnp.

I can open a bug report in the Yarn repo for this but are you sure this is an problem with Yarn and not vscode-eslint or eslint? It looks like your repro works in node if you specify .pnp.loader.mjs as a loader which you always have to do with Yarn according to this comment: https://github.com/yarnpkg/berry/issues/5241#issuecomment-1414226426:

PS D:\vscode-eslint-test> $env:NODE_PATH
.yarn\sdks
PS D:\vscode-eslint-test> node -r ./.pnp.cjs --loader ./.pnp.loader.mjs
Welcome to Node.js v20.12.0.
Type ".help" for more information.
> .load bug.cjs
const library = require(`eslint`);

async function main() {
  const eslint = new (await library.loadESLint({ useFlatConfig: true }))();
  const report = await eslint.lintText(`var test = "hello";`);
  console.log(report);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Promise {
  <pending>,
  [Symbol(async_id_symbol)]: 137,
  [Symbol(trigger_async_id_symbol)]: 134
}
> [
  {
    filePath: '<text>',
    messages: [],
    suppressedMessages: [],
    errorCount: 0,
    fatalErrorCount: 0,
    warningCount: 0,
    fixableErrorCount: 0,
    fixableWarningCount: 0,
    usedDeprecatedRules: [Getter]
  }
]

It also seems to work if you start node with yarn node without a loader argument because then the loader is registered automatically I think. I thought that this loader was injected in the node runtime by the vscode eslint sdk package that gets installed when you run the yarn dlx @yarnpkg/sdks vscode command so that it should be able to use ESM? From https://yarnpkg.com/getting-started/editor-sdks:

“The SDKs workaround that by generating indirection packages. When required, these indirection automatically setup the loader before forwarding the require calls to the real packages.”

I was able to reproduce this. It seems that you need to have corepack enabled so that yarn does the right thing.

This being said I do think that this comes from some PnP stuff yarn does. I can reproduce the same problem using simple eslint API. The following code:

PowerShell:

> $env:NODE_PATH=".yarn/sdks"

This is what yarn does when running the editor SDK for VS Code (see https://yarnpkg.com/getting-started/editor-sdks)

const library = require(`eslint`);

async function main() {
	const eslint = new (await library.loadESLint({ useFlatConfig: true }))();
	const report = await eslint.lintText(`var test = "hello";`);
	console.log(report);
}

main().catch((error) => {
	console.error(error);
	process.exit(1);
});

produces the exact same error:

bugs\vscode-eslint-test [main ≡ +0 ~1 -0 | +1 ~1 -0 !]> node
Welcome to Node.js v18.18.2.
Type ".help" for more information.
> const library = require(`eslint`);
undefined
> library
{
  Linter: [class Linter],
  loadESLint: [AsyncFunction: loadESLint],
  ESLint: [class ESLint] { configType: 'eslintrc' },
  RuleTester: [class RuleTester] {
    [Symbol(itOnly)]: null,
    [Symbol(it)]: null,
    [Symbol(describe)]: null
  },
  SourceCode: [class SourceCode extends TokenStore]
}
> async function main() { const eslint = new (await library.loadESLint({ useFlatConfig: true }))(); const report = await eslint.lintText(`var test = "hello";`); console.log(repo
rt); }
undefined
> main().catch((error) => { console.error(error); process.exit(1); });
Promise {
  <pending>,
  [Symbol(async_id_symbol)]: 117,
  [Symbol(trigger_async_id_symbol)]: 114
}
> Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@eslint/js' imported from C:\xxxxxxxxx\bugs\vscode-eslint-test\eslint.config.mjs
Did you mean to import @eslint-js-npm-8.57.0-00ead3710a-10c0.zip/node_modules/@eslint/js/src/index.js?
    at new NodeError (node:internal/errors:405:5)
    at packageResolve (node:internal/modules/esm/resolve:890:9)
    at moduleResolve (node:internal/modules/esm/resolve:939:20)
    at defaultResolve (node:internal/modules/esm/resolve:1132:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:835:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}
bugs\vscode-eslint-test [main ≡ +0 ~1 -0 | +1 ~1 -0 !]>

IMO this is something the yarn people have to look into.

@dbaeumer That is strange because I get the same error with 3.0.5 as I did with 3.0.3 with the mjs config. test.ts doesn’t show the linting errors and the output log logs this error:

[Info  - 2:27:54 PM] ESLint server is starting.
[Info  - 2:27:54 PM] ESLint server running in node v18.17.1
[Info  - 2:27:54 PM] ESLint server is running.
[Info  - 2:27:55 PM] ESLint library loaded from: D:\vscode-eslint-test\.yarn\sdks\eslint\lib\api.js
[Error - 2:27:55 PM] Calculating config file for file:///d%3A/vscode-eslint-test/test.ts) failed.
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@eslint/js' imported from D:\vscode-eslint-test\eslint.config.mjs
Did you mean to import @eslint-js-npm-8.57.0-00ead3710a-10c0.zip/node_modules/@eslint/js/src/index.js?
    at new NodeError (node:internal/errors:405:5)
    at packageResolve (node:internal/modules/esm/resolve:893:9)
    at moduleResolve (node:internal/modules/esm/resolve:942:20)
    at defaultResolve (node:internal/modules/esm/resolve:1135:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:835:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36)

Perhaps there’s something with my environment or Yarn installation that’s causing this issue then. I just don’t understand what it could be. I also noticed @bppdddqqqq had the same problem as me according to a comment here: https://github.com/microsoft/vscode-eslint/issues/1644#issuecomment-2001964277 but I think that was with an older version.

Ran into the same issue today, weirdly enough what solved it for me was renaming the eslint.config.js files in my workspace projects to eslint.config.cjs (and porting them to CommonJS by changing import to require, export to module.exports etc) and adding this to my workspace config:

  "eslint.experimental.useFlatConfig": true,
  "eslint.workingDirectories": [{ "mode": "auto" }]

Seems like the .js and .mjs variants don’t get loaded properly by the extension but .cjs works fine. My packages all have "type": "module" in their package.json so everything else is ESM except this…

locateConfigFileToUse() is getting passed the root directory that vscode has opened instead of the directory that contains package.json. If you use vscode to open the directory that has package.json immediately inside it as the root it will find the file (or an even deeper directory, since it goes up until it finds the config).

You need to set the eslint.workingDirectories setting correctly if you are encountering this. Eslint used to look for a config file relative to the file you are linting, now I guess this call is required. I’m not sure what, if anything, this extension can do to make this less painful in the future, it kind of seems like eslint screwed us over.

Just wanted to share what I have that is working:

// settings.json
{
	"[javascript][javascriptreact][typescript][typescriptreact]": {
		"editor.codeActionsOnSave": {
			"source.fixAll.eslint":true
		},
		"editor.formatOnSave": true
	},
	"editor.formatOnSave": false,
	"eslint.experimental.useFlatConfig": true,
	"eslint.format.enable": true,
	// For projects that need more memory, you need BOTH of these:
	//"eslint.execArgv": ["--max-old-space-size=16384"],
	//"eslint.runtime": "node",
}
// eslint.config.js
import eslint from "@eslint/js";
import typescriptParser from "@typescript-eslint/parser";
import typescriptPlugin from "@typescript-eslint/eslint-plugin";

export default [
    // Enable baseline ruleset
    {
        "files": ["**/*.ts"],
        "ignores": ["**/node_modules/*"],
        "rules": eslint.configs.recommended.rules
    },

    // Disable rules that are incompatible with or better handled by TypeScript
    {
        "rules": typescriptPlugin.configs["eslint-recommended"].overrides[0].rules
    },

    // Enable TypeScript-specific rules
    {
        "plugins": {
            "@typescript-eslint": typescriptPlugin
        },
        "languageOptions": {
            "parser": typescriptParser,
            "parserOptions": {
                "project": "./tsconfig.json"
            }
        },
        "rules": {
            ...typescriptPlugin.configs.recommended.rules
           // your overrides here
        }
    }
];

Thanks! Moving it to local settings did make that error go away, but the linting still isn’t showing in the UI.

@dbaeumer - Sorry for the delay, it’s been a crazy day, but I just updated that repo to reproduce the same issue.

So currently I have it setup to use the new flat config and it lints from the command line, but on the UI, I am getting the same “unexpected key” error as show above. I also have it setup to swap to the old .eslintrc* style config and that works both on the command line and UI. If you want to switch between the two, in /packages/eslint-config/package.json change the “main” export file to oldConfig.cjs, then in /apps/client/package.json update the lint script to use flat config to be false and then (obviously) disable the setting in VSCode and restart the ESLint server and you will see that both sides work fine. However, with the flat config, the CLI works, but the UI gives that error.

Hopefully that makes sense, but just shout at me if I can provide any additional information. And thank you again for investigating this issue!

I has been excluded by my .gitignore but I do have it both of my repos. I have updated the repos with my .vscode/settings.json

EDIT

I have tried to open VSCode at /test which is not a monorepo, just the folder where I have both of my monorepos. ESLint doesn’t work at all.

test/
   /eslint-flat-vscode-root
   /eslint-flat-vscode-playground

It seems like the extension has issue traversing the project tree to look for the closest eslint.config.js