ale icon indicating copy to clipboard operation
ale copied to clipboard

eslint with yarn pnp

Open chumager opened this issue 5 years ago • 16 comments

Information

VIM version

VIM - Vi IMproved 8.1 (2018 May 18, compiled Jun 15 2019 16:41:15) Parches incluidos: 1-875, 878, 884, 948, 1046, 1365-1368, 1382, 1401

Operating System: Debian Buster 10.2

What went wrong

Hi, for some time I've been using yarn v2 or yarn with pnp (depending the ptoject), eslint is installed as a devDependency, so to use eslint I must execute yarn run eslint, to solve this with ALE I've set this variables:

let g:ale_javascript_eslint_executable = 'yarn'
let g:ale_javascript_eslint_options = 'run eslint'

No problem for a long time, few days ago I updated ALE, through vim-plug and the linter stop working, after doing some search in the source core and :AleInfo, I realized now Ale search for the node_modules path to execute eslint, but for me it doesn't work because I don't have node_modules directory.

I don't know if there is a way con configure ale to work with yarn pnp but if one sets a executable for eslint, ALE shouldn't search for node_modules, neither change the path before eslint execution.

For now I just commented the l:cd_command in the return from function! ale#handlers#eslint#GetCommand(buffer) abort

Reproducing the bug

The root directory is ~jcmunoz/DropBox/Develop/node.js/tests

mkdir ale_test
cd ale_test
yarn init -y
yarn --pnp
yarn -D add eslint eslint-plugin-prettier prettier

add package.json config for eslint:

  "eslintConfig": {
    "root": true,
    "env": {
      "node": true,
      "es2017": true,
      "commonjs": true
    },
    "plugins": [
      "prettier"
    ],
    "extends": [
      "eslint:recommended"
    ],
    "rules": {
      "no-console": 0,
      "max-len": [
        "error",
        {
          "code": 120,
          "ignoreComments": true
        }
      ],
      "prettier/prettier": [
        "warn",
        {
          "printWidth": 120,
          "tabWidth": 2,
          "bracketSpacing": false
        }
      ]
    },
    "parserOptions": {
      "ecmaVersion": 2018,
      "ecmaFeatures": {
        "jsx": true,
        "es6": true
      },
      "sourceType": "script"
    }
  }

edit a new file test.js vim test.js add simple code:

"use strict";
let test = 1;

:ALEInfo

Current Filetype: javascript Available Linters: ['eslint', 'fecs', 'flow', 'flow-language-server', 'jscs', 'jshint', 'standard', 'tsserver', 'xo'] Enabled Linters: ['eslint', 'fecs', 'flow', 'flow-language-server', 'jscs', 'jshint', 'standard', 'tsserver', 'xo'] Suggested Fixers: 'eslint' - Apply eslint --fix to a file. 'fecs' - Apply fecs format to a file. 'importjs' - automatic imports for javascript 'prettier' - Apply prettier to a file. 'prettier_eslint', 'prettier-eslint' - Apply prettier-eslint to a file. 'prettier_standard', 'prettier-standard' - Apply prettier-standard to a file. 'remove_trailing_lines' - Remove all blank lines at the end of a file. 'standard' - Fix JavaScript files using standard --fix 'trim_whitespace' - Remove all trailing whitespace characters at the end of every line. 'xo' - Fix JavaScript/TypeScript files using xo --fix. Linter Variables:

let g:ale_javascript_eslint_executable = 'yarn' let g:ale_javascript_eslint_options = 'run eslint' let g:ale_javascript_eslint_suppress_eslintignore = 0 let g:ale_javascript_eslint_suppress_missing_config = 0 let g:ale_javascript_eslint_use_global = 0 let g:ale_javascript_fecs_executable = 'fecs' let g:ale_javascript_fecs_use_global = 0 let g:ale_javascript_flow_executable = 'flow' let g:ale_javascript_flow_ls_executable = 'flow' let g:ale_javascript_flow_ls_use_global = 0 let g:ale_javascript_flow_use_global = 0 let g:ale_javascript_flow_use_home_config = 0 let g:ale_javascript_flow_use_respect_pragma = 1 let g:ale_javascript_jscs_executable = 'jscs' let g:ale_javascript_jscs_use_global = 0 let g:ale_javascript_jshint_executable = 'jshint' let g:ale_javascript_jshint_use_global = 0 let g:ale_javascript_prettier_options = '--tab-width=2' let g:ale_javascript_prettier_use_local_config = 1 let g:ale_javascript_standard_executable = 'standard' let g:ale_javascript_standard_options = '' let g:ale_javascript_standard_use_global = 0 let g:ale_javascript_tsserver_config_path = '' let g:ale_javascript_tsserver_executable = 'tsserver' let g:ale_javascript_tsserver_use_global = 0 let g:ale_javascript_xo_executable = 'xo' let g:ale_javascript_xo_options = '' let g:ale_javascript_xo_use_global = 0 Global Variables:

let g:ale_cache_executable_check_failures = v:null let g:ale_change_sign_column_color = 0 let g:ale_command_wrapper = '' let g:ale_completion_delay = v:null let g:ale_completion_enabled = 0 let g:ale_completion_max_suggestions = v:null let g:ale_echo_cursor = 1 let g:ale_echo_msg_error_str = 'Error' let g:ale_echo_msg_format = '%code: %%s' let g:ale_echo_msg_info_str = 'Info' let g:ale_echo_msg_warning_str = 'Warning' let g:ale_enabled = 1 let g:ale_fix_on_save = 0 let g:ale_fixers = {'json5': ['prettier'], 'vue': ['eslint'], 'json': ['prettier'], 'html': ['prettier'], 'javascript': ['eslint']} let g:ale_history_enabled = 1 let g:ale_history_log_output = 1 let g:ale_keep_list_window_open = 0 let g:ale_lint_delay = 200 let g:ale_lint_on_enter = 1 let g:ale_lint_on_filetype_changed = 1 let g:ale_lint_on_insert_leave = 1 let g:ale_lint_on_save = 1 let g:ale_lint_on_text_changed = 'normal' let g:ale_linter_aliases = {} let g:ale_linters = {} let g:ale_linters_explicit = 0 let g:ale_list_vertical = 0 let g:ale_list_window_size = 10 let g:ale_loclist_msg_format = '%code: %%s' let g:ale_lsp_root = {} let g:ale_max_buffer_history_size = 20 let g:ale_max_signs = -1 let g:ale_maximum_file_size = v:null let g:ale_open_list = 1 let g:ale_pattern_options = v:null let g:ale_pattern_options_enabled = v:null let g:ale_set_balloons = 0 let g:ale_set_highlights = 1 let g:ale_set_loclist = 1 let g:ale_set_quickfix = 0 let g:ale_set_signs = 1 let g:ale_sign_column_always = 0 let g:ale_sign_error = '>>' let g:ale_sign_info = '--' let g:ale_sign_offset = 1000000 let g:ale_sign_style_error = '>>' let g:ale_sign_style_warning = '--' let g:ale_sign_warning = '--' let g:ale_sign_highlight_linenrs = 0 let g:ale_statusline_format = v:null let g:ale_type_map = {} let g:ale_use_global_executables = v:null let g:ale_virtualtext_cursor = 0 let g:ale_warn_about_trailing_blank_lines = 1 let g:ale_warn_about_trailing_whitespace = 1 Command History:

(executable check - success) yarn (finished - exit code 1) ['/bin/bash', '-c', 'cd ''/home/jcmunoz/Dropbox/Develop/node.js'' && ''yarn'' run eslint -f json --stdin --stdin-filename ''/home/jcmunoz/Dropbox/Develop/node.js/tests/ale_test/test.js'' < ''/tmp/vShsxKu/4/test.js''']

<<<OUTPUT STARTS>>> yarn run v1.21.1 error Couldn't find a package.json file in "/home/jcmunoz/Dropbox/Develop/node.js" info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. <<<OUTPUT ENDS>>>

(executable check - failure) fecs (executable check - failure) jscs (executable check - failure) jshint (executable check - failure) standard (executable check - failure) tsserver (executable check - failure) xo

chumager avatar Jan 14 '20 16:01 chumager

A note on timeliness here: this is probably not affecting many users at the moment, but will be a much larger issue when the next version of yarn drops. That's currently scheduled for February 1st, and I'd expect an influx of this bug to be reported as soon as devs start updating yarn after that date.

NAlexPear avatar Jan 25 '20 17:01 NAlexPear

As far as I can tell, there's a few things that can help:

  • yarn bin lists all the bin commands from packages. It is slow, but according to @arcanis it's the only reliable way to know which bin command is available.
  • some common binaries (eslint, tsserver, ...) are exposed in .vscode/pnpify directory (https://github.com/yarnpkg/berry/pull/783) when running yarn pnpify --sdk See doc
  • using yarn unplug pkg-name it is possible to uncompress a module in .yarn/unplugged/pkg-name
  • .node_modules directory is constantly cleared
  • Creating a file with the following content in the project can be a workaround to re-create executables
#!/usr/bin/env bash
# naming this file `tsserver` for instance will run `tsserver` binary
COMMAND="${BASH_SOURCE[0]##*/}"
yarn run "$COMMAND" "$@"

There's still something I miss on the ALE side, as I'm forcing the b:javascript_prettier_executable (and others) but ALE still fails to check for executables.

themouette avatar Jan 27 '20 11:01 themouette

I managed to fix this by also setting use_global:

g:ale_javascript_eslint_use_global = 1
g:ale_javascript_eslint_executable = 'yarn'
g:ale_javascript_eslint_options = 'run eslint'

This overrides ALE's node_modules path searching logic and simply use the executable setting. See https://github.com/dense-analysis/ale/blob/v2.6.0/autoload/ale/node.vim#L11

EDIT: This assumes all your JavaScript projects use a local per-project installation of yarn as detailed in https://yarnpkg.com/getting-started/install

zzzaim avatar Feb 19 '20 11:02 zzzaim

Thanks... it's a workaround...

Regards.

chumager avatar Feb 21 '20 18:02 chumager

I was able to work around this even easier (although somewhat less ideal) by just creating an empty node_modules/ This just prevents ale from cding out of my project directory and works great because yarn run eslint works as expected.

Maybe ALE could just check for .pnp.js or something?

infime avatar Mar 02 '20 15:03 infime

@chumager Could we please reopen this? Here's a PR that finds .pnp.js if it can't find node_modules. https://github.com/dense-analysis/ale/pull/3024

infime avatar Mar 02 '20 20:03 infime

The stale bot closed that pull request, and it didn't have any tests associated with it. If anyone can submit a pull request with tests, I'll have a look at it.

w0rp avatar Aug 29 '20 16:08 w0rp

fwiw, this affects all Node.js utilities, not just eslint.

shannonmoeller avatar Nov 10 '20 19:11 shannonmoeller

Something very similar happens when using pnpm. pnpm creates a hidden directory in node_modules and links things up to node_modules

config/release-config-logdna/node_modules/
├── @eslint
│   └── eslintrc -> ../.pnpm/@eslint/[email protected]/node_modules/@eslint/eslintrc
├── eslint -> .pnpm/[email protected]/node_modules/eslint
├── eslint-config-logdna -> .pnpm/[email protected][email protected]/node_modules/eslint-config-logdna
├── eslint-plugin-es -> .pnpm/[email protected][email protected]/node_modules/eslint-plugin-es
├── eslint-plugin-logdna -> .pnpm/[email protected]/node_modules/eslint-plugin-logdna
├── eslint-plugin-node -> .pnpm/[email protected][email protected]/node_modules/eslint-plugin-node
├── eslint-plugin-sensible -> .pnpm/[email protected]/node_modules/eslint-plugin-sensible
├── eslint-scope -> .pnpm/[email protected]/node_modules/eslint-scope
├── eslint-utils -> .pnpm/[email protected]/node_modules/eslint-utils
├── eslint-visitor-keys -> .pnpm/[email protected]/node_modules/eslint-visitor-keys
└── tap -> .pnpm/[email protected]/node_modules/tap

Seems like ale is following the link and this is setting the root context that is passed to plugins as the resolved path

  Command History:
(executable check - success) /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/bin/eslint.js
(finished - exit code 2) ['/bin/bash', '-c', 'cd ''/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]'' && ''/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/bin/eslint.js'' -f json --stdin --stdin-filen
ame ''/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/index.js'' < ''/tmp/nvimCg4oSz/3/index.js''']
<<<OUTPUT STARTS>>>
Oops! Something went wrong! :(
ESLint: 7.16.0
Error: Error while loading rule 'sensible/check-require': Cannot find module '/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/package.json'
Require stack:
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint-plugin-sensible/lib/rules/check-require.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/requireindex/index.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint-plugin-sensible/lib/index.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/@eslint/[email protected]/node_modules/@eslint/eslintrc/lib/config-array-factory.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/@eslint/[email protected]/node_modules/@eslint/eslintrc/lib/index.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/cli-engine/cli-engine.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/eslint/eslint.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/eslint/index.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/cli.js
- /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/bin/eslint.js
Occurred while linting /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/index.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
    at Function.Module._load (internal/modules/cjs/loader.js:841:27)
    at Module.require (internal/modules/cjs/loader.js:1025:19)
    at require (/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at Object.create (/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint-plugin-sensible/lib/rules/check-require.js:59:23)
    at createRuleListeners (/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/linter/linter.js:761:21)
    at /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/linter/linter.js:931:31
    at Array.forEach (<anonymous>)
    at runRules (/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/linter/linter.js:876:34)
    at Linter._verifyWithoutProcessors (/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]/node_modules/eslint/lib/linter/linter.js:1173:31)
<<<OUTPUT ENDS>>>
'/bin/bash', '-c', 'cd ''/home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/.pnpm/[email protected]''

Rather than cd to /home/esatterwhite/dev/js/logdna/tooling-semantic-release/config/release-config-logdna/node_modules/eslint

It may be a weird behavior in some of the plugins trying to resolve package.json as well, but it works as expected outside of ALE by just running npm run eslint

esatterwhite avatar Dec 30 '20 22:12 esatterwhite

I also had a similar problem with pnpm (though not with pnp enabled) in that ale resolves eslint but doesn't respect the eslintignore. I fixed it by putting the ignore pattern in the eslintrc. This is a workaround but might help anyone that comes across this.

johnpangalos avatar Feb 12 '21 11:02 johnpangalos

I found another workaround that gets eslint, prettier, and flow-bin working on my machine without incurring the cost of yarn run and yarn exec. It should work for typescript as well, but I haven't tested that as I don't use it in conjunction with yarn 2.

  1. Run yarn add --dev eslint prettier flow-bin in the root of your project
  2. Run yarn dlx @yarnpkg/pnpify --sdk vim
  3. Add the following to your .vimrc:
let g:ale_completion_enabled = 1
let g:ale_fix_on_save = 1
let g:ale_fixers = { 'javascript': ['eslint', 'prettier'] }
let g:ale_linters = { 'javascript': ['eslint', 'flow-language-server'] }

let s:sdks = finddir('.yarn/sdks', ';')
if !empty(s:sdks)
  let g:ale_javascript_eslint_use_global = 1
  let g:ale_javascript_eslint_executable = s:sdks . '/eslint/bin/eslint.js'
  let g:ale_javascript_flow_ls_use_global = 1
  let g:ale_javascript_flow_ls_executable = s:sdks . '/flow-bin/cli.js'
  let g:ale_javascript_prettier_use_global = 1
  let g:ale_javascript_prettier_executable = s:sdks . '/prettier/index.js'
endif

[UPDATE]: If anyone is still having issues, you may be hitting an edge case I've solved in #3684.

shannonmoeller avatar Apr 07 '21 16:04 shannonmoeller

@shannonmoeller, very timely! I just ran into this problem today and that fix works perfectly. Unfortunately simply unpluging the package didn't seem to work for me, but the sdk solution is likely cleaner anyways.

Thank you!

Chalks avatar Apr 07 '21 22:04 Chalks

I was able to get typescript working using

  let g:ale_javascript_tsserver_use_global = 1
  let g:ale_typescript_tsserver_executable = s:sdks . '/typescript/bin/tsserver'

marcusglowe avatar May 28 '21 03:05 marcusglowe

pnpm, because two package managers were not enough.

w0rp avatar Jun 19 '21 11:06 w0rp

Also ran into this using the eslint parserOptions.project configuration:

        "parserOptions": {
          "project": "./tsconfig.json"
        },

This yields an error for a missing file: [{"filePath":"/Users/<path>/Button.tsx","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: Cannot read file '/users/<path>/node_modules/.pnpm/[email protected]/tsconfig.json'."}]. This also fails in similar fashion if you use the related parserOptions.tsconfigRootDir option.

I can get around this by changing the relative path to an absolute path, but that doesn't work too well given that the path in question will be different on different users' machines 😅

Open to any suggestions here


Looking at this further, this is the command that is failing:

(finished - exit code 1) ['/bin/zsh', '-c', 'cd ''/Users/natewillard/Vesta/vesta/node_modules/.pnpm/[email protected]'' && ''/Users/natewillard/Vesta/vesta/node_modules/.pnpm/[email protected]/node_modules/eslint/bin/eslint.js'' -f json --stdin --stdin-filename ''/Users/natewillard/Vesta/vesta/client/src/components/shared/Button.tsx'' < ''/var/folders/m9/2f8cklmx3db81cf6vj5nlq6r0000gn/T/vrZg2lZ/17/Button.tsx''']

EDIT: I've found a solution to this!

  1. You can use parserOptions.tsconfigRootDir: __dirname
  2. You will probably need to make sure you're using an eslintrc.js file instead of a .json in order to use __dirname

RandomSeeded avatar May 23 '22 23:05 RandomSeeded

I'm still having this issue, even using the solutions here. When I run yarn eslint, I get the expected deluge of errors (because I'm lazy and don't want to manually run the CLI), but in vim I get errors about missing imports, which are not from ALE. I think they're from YouCompleteMe, but I have to debug further.

Diving into the output of ALEInfo, I see that the executable check for ESLint succeeds. ALE now seems smart enough not to need @shannonmoeller's solution:

(executable check - success) /.yarn/sdks/eslint/bin/eslint.js

But running it fails with an exit code of 1:

(finished - exit code 1) ['/bin/sh', '-c', 'cd '''' && ''/.yarn/sdks/eslint/bin/eslint.js'' -f json --stdin --stdin-filename ''/app/frontend/application/src/components/Table/Table.tsx'' < ''/var/folders/6c/0s9lv71x4sd__5ly4rbhwr540000gn/T/v5804eK/1/Table.tsx''']

Running this myself, I get an obscure error that I think is related to ESM modules vs. CommonJS modules, but I'm not sure how that is possible:

<project folder>/.pnp.cjs:15801
    throw firstError;
    ^

Error: Package subpath './bin/eslint.js' is not defined by "exports" in <project folder>/.yarn/cache/eslint-npm-8.49.0-32a1f359b3-4dfe257e1e.zip/node_modules/eslint/package.json imported from <project folder>/
Require stack:
- <project folder>/.pnp.cjs
    at require$$0.Module._resolveFilename (<project folder>/.pnp.cjs:15800:13)
    at Module._load (node:internal/modules/cjs/loader:901:27)
    at require$$0.Module._load (<project folder>/.pnp.cjs:15689:31)
    at Module.require (node:internal/modules/cjs/loader:1115:19)
    at require (node:internal/modules/helpers:130:18)
    at Object.<anonymous> (<project folder>/.yarn/sdks/eslint/bin/eslint.js:20:18)
    at Module._compile (node:internal/modules/cjs/loader:1233:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1287:10)
    at Module.load (node:internal/modules/cjs/loader:1091:32)
    at Module._load (node:internal/modules/cjs/loader:938:12)

Node.js v20.5.1

TSServer is similarly successful in the executable check, and it actually starts:

(executable check - success) <project folder>/.yarn/sdks/typescript/bin/tsserver
(started) ['/bin/sh', '-c', '''<project folder>/.yarn/sdks/typescript/bin/tsserver''']

trisys3 avatar Nov 03 '23 15:11 trisys3