dense-analysis / ale

Check syntax in Vim/Neovim asynchronously and fix files, with Language Server Protocol (LSP) support
BSD 2-Clause "Simplified" License
13.55k stars 1.43k forks source link

Some executables with spaces don't work? #1292

Closed Craige closed 5 years ago

Craige commented 6 years ago

Information

Executable name for linters do not perform correctly when they contain a space. Eg: bin/passthrough phpcs fails to append parameters and flags to the command where as bin/phpcs-passthrough succeeds.

VIM version VIM - Vi IMproved 8.0 (2016 Sep 12, compiled Aug 29 2017 23:29:43) MacOS X (unix) version

Operating System: WHAT OS WERE YOU USING? OS X 10.12.6

:ALEInfo

PASTE OUTPUT OF :ALEInfo HERE. YOU CAN TRY :ALEInfoToClipboard.


 Current Filetype: php
Available Linters: ['hack', 'langserver', 'phan', 'php', 'phpcs', 'phpmd', 'phpstan']
  Enabled Linters: ['hack', 'langserver', 'phan', 'php', 'phpcs', 'phpmd', 'phpstan']
 Linter Variables:

let g:ale_php_langserver_executable = 'php-language-server.php'
let g:ale_php_langserver_use_global = 0
let g:ale_php_phan_minimum_severity = 0
let g:ale_php_phpcs_executable = 'bin/vm phpcs'
let g:ale_php_phpcs_standard = ''
let g:ale_php_phpcs_use_global = 1
let g:ale_php_phpmd_executable = 'phpmd'
let g:ale_php_phpmd_ruleset = 'cleancode,codesize,controversial,design,naming,unusedcode'
let g:ale_php_phpstan_configuration = ''
let g:ale_php_phpstan_executable = 'phpstan'
let g:ale_php_phpstan_level = '4'
 Global Variables:

let g:ale_cache_executable_check_failures = 0
let g:ale_change_sign_column_color = 0
let g:ale_command_wrapper = ''
let g:ale_completion_delay = 100
let g:ale_completion_enabled = 0
let g:ale_completion_max_suggestions = 50
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 = {}
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_save = 1
let g:ale_lint_on_text_changed = 'always'
let g:ale_lint_on_insert_leave = 0
let g:ale_linter_aliases = {}
let g:ale_linters = {}
let g:ale_linters_explicit = 0
let g:ale_list_window_size = 10
let g:ale_loclist_msg_format = '%code: %%s'
let g:ale_max_buffer_history_size = 20
let g:ale_max_signs = -1
let g:ale_maximum_file_size = 0
let g:ale_open_list = 0
let g:ale_pattern_options = {}
let g:ale_pattern_options_enabled = 0
let g:ale_set_balloons = 1
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_statusline_format = ['%d error(s)', '%d warning(s)', 'OK']
let g:ale_type_map = {}
let g:ale_warn_about_trailing_blank_lines = 1
let g:ale_warn_about_trailing_whitespace = 1
  Command History:

(executable check - failure) hh_client
(executable check - failure) phan
(executable check - success) php
(started) ['/usr/local/bin/zsh', '-c', 'php -l -d error_reporting=E_ALL -d display_errors=1 -d log_errors=0 -- < ''/var/folders/lm/hx1gkxzs1_q3gk8x_n22qfhc0000gn/T/vimBCBI/2/App.php''']
(executable check - failure) bin/passthrough phpcs
(executable check - failure) phpmd
(executable check - failure) phpstan
(executable check - failure) hh_client
(executable check - failure) phan
(finished - exit code 0) ['/usr/local/bin/zsh', '-c', 'php -l -d error_reporting=E_ALL -d display_errors=1 -d log_errors=0 -- < ''/var/folders/lm/hx1gkxzs1_q3gk8x_n22qfhc0000gn/T/vimBCBI/3/App.php''']

<<<OUTPUT STARTS>>>
No syntax errors detected in -
<<<OUTPUT ENDS>>>

(executable check - failure) bin/passthrough phpcs
(executable check - failure) phpmd
(executable check - failure) phpstan

vs

 Current Filetype: php
Available Linters: ['hack', 'langserver', 'phan', 'php', 'phpcs', 'phpmd', 'phpstan']
  Enabled Linters: ['hack', 'langserver', 'phan', 'php', 'phpcs', 'phpmd', 'phpstan']
 Linter Variables:

let g:ale_php_langserver_executable = 'php-language-server.php'
let g:ale_php_langserver_use_global = 0
let g:ale_php_phan_minimum_severity = 0
let g:ale_php_phpcs_executable = 'bin/phpcs-passthrough'
let g:ale_php_phpcs_standard = ''
let g:ale_php_phpcs_use_global = 1
let g:ale_php_phpmd_executable = 'phpmd'
let g:ale_php_phpmd_ruleset = 'cleancode,codesize,controversial,design,naming,unusedcode'
let g:ale_php_phpstan_configuration = ''
let g:ale_php_phpstan_executable = 'phpstan'
let g:ale_php_phpstan_level = '4'
 Global Variables:

let g:ale_cache_executable_check_failures = 0
let g:ale_change_sign_column_color = 0
let g:ale_command_wrapper = ''
let g:ale_completion_delay = 100
let g:ale_completion_enabled = 0
let g:ale_completion_max_suggestions = 50
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 = {}
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_save = 1
let g:ale_lint_on_text_changed = 'always'
let g:ale_lint_on_insert_leave = 0
let g:ale_linter_aliases = {}
let g:ale_linters = {}
let g:ale_linters_explicit = 0
let g:ale_list_window_size = 10
let g:ale_loclist_msg_format = '%code: %%s'
let g:ale_max_buffer_history_size = 20
let g:ale_max_signs = -1
let g:ale_maximum_file_size = 0
let g:ale_open_list = 0
let g:ale_pattern_options = {}
let g:ale_pattern_options_enabled = 0
let g:ale_set_balloons = 1
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_statusline_format = ['%d error(s)', '%d warning(s)', 'OK']
let g:ale_type_map = {}
let g:ale_warn_about_trailing_blank_lines = 1
let g:ale_warn_about_trailing_whitespace = 1
  Command History:

(executable check - failure) hh_client
(executable check - failure) phan
(executable check - success) php
(started) ['/usr/local/bin/zsh', '-c', 'php -l -d error_reporting=E_ALL -d display_errors=1 -d log_errors=0 -- < ''/var/folders/lm/hx1gkxzs1_q3gk8x_n22qfhc0000gn/T/vZhpgPU/2/App.php''']
(executable check - success) bin/phpcs-passthrough
(started) ['/usr/local/bin/zsh', '-c', '''bin/phpcs-passthrough'' -s --report=emacs --stdin-path=''<redacted>/App.php''  < ''/var/folders/lm/hx1gkxzs1_q3gk8x_n22qfhc0000gn/T/vZhpgPU/3/App.php''']
(executable check - failure) phpmd
(executable check - failure) phpstan
(executable check - failure) hh_client
(executable check - failure) phan
(finished - exit code 0) ['/usr/local/bin/zsh', '-c', 'php -l -d error_reporting=E_ALL -d display_errors=1 -d log_errors=0 -- < ''/var/folders/lm/hx1gkxzs1_q3gk8x_n22qfhc0000gn/T/vZhpgPU/4/App.php''']

<<<OUTPUT STARTS>>>
No syntax errors detected in -
<<<OUTPUT ENDS>>>

(finished - exit code 0) ['/usr/local/bin/zsh', '-c', '''bin/phpcs-passthrough'' -s --report=emacs --stdin-path=''<redacted>/App.php''  < ''/var/folders/lm/hx1gkxzs1_q3gk8x_n22qfhc0000gn/T/vZhpgPU/5/App.php''']

<<<OUTPUT STARTS>>>

What went wrong

ALE broke when the executable command contain a space (technically, the "command" was a path to a script and a parameter to pass to trigger a subroutine). This is undesirable.

Reproducing the bug

Steps for repeating the bug:

  1. Use a linter path with a space in it. Eg. a script that passes through the linter to run on a Virtual Machine and accepts a parameter

bin/stript parameter-denoting-subroutine-to-trigger

w0rp commented 6 years ago

I can't repeat the bug here. You'll have to let me know which linter specifically this is a problem for. The executable path is escaped for phpcs.

Craige commented 6 years ago

I was having this problem specifically with phpcs

w0rp commented 6 years ago

Can you include the output when this is a problem? Note that the file containing the space in the path will have to be executable for the command to run.

Craige commented 6 years ago

How do I get the output beyond what I've included above?

w0rp commented 6 years ago

Just :ALEInfo is enough. If the executable check fails, that means the file wasn't executable, so the command wasn't run.

Craige commented 6 years ago

I posted the :ALEInfo output in the initial ticket...

neersighted commented 5 years ago

Unless @w0rp has a different opinion, I think this is a non-issue (and thus wontfix).

What's going on here is simple: you've passed a command to ALE, expending it to pass the second, space-separated component as an argument, and thus invoke a subcommand. What is instead happening is that ALE is searching for a command with a space in the filename.

More simply put, ALE is executing 'bin/passthrough phpcs', and you want it to execute 'bin/passthrough' 'phpcs'.

This is a quirk of *nix filesystems in general, and shell argument splitting in general. Other common tools, such as git, behave the same here (try setting $EDITOR to a command with spaces in it), and as such I think that escaping any and all spaces in the command is the expected behavior (and thus acting like exec(2), instead of a shell).

If you want to avoid code duplication in your wrapper scripts, a common Unix idiom is dynamic dispatch based on argv[0]. This variable ($0 in POSIX sh) is populated with the exact string used to invoke your script, and you can use it to change your script's behavior, not unlike a subcommand. Simply symlink your wrapper to different names, and you can invoke as many variations as you want.

Let me know if you have any additional questions! I'll probably try to follow up with better documentation of how ALE parses commands in the future.

w0rp commented 5 years ago

I think a general documentation entry explaining how shell escaping works would be fine, which we could reference in every _executable documentation section. Executable paths are quoted, and that is how it's supposed to work. I think specific issues with specific linters can be handled separately, such as adding support for running scripts via other tools. There is a general option for wrapping any command in scripts, which works to some degree. See :help g:ale_command_wrapper.