diff --git a/ale_linters/elixir/mix.vim b/ale_linters/elixir/mix.vim index 948c6d3648..76380ef357 100644 --- a/ale_linters/elixir/mix.vim +++ b/ale_linters/elixir/mix.vim @@ -1,45 +1,88 @@ " Author: evnu - https://github.com/evnu " Author: colbydehart - https://github.com/colbydehart +" Author: toupeira - https://github.com/toupeira " Description: Mix compile checking for Elixir files function! ale_linters#elixir#mix#Handle(buffer, lines) abort - " Matches patterns like the following: + " Example output from Elixir 1.20.0: " - " Error format - " ** (CompileError) apps/sim/lib/sim/server.ex:87: undefined function update_in/4 + " warning: variable "a" is unused (if the variable is not meant to be used, prefix it with an underscore) + " │ + " 16 │ a = missing_var + " │ ~ + " │ + " └─ lib/foo.ex:16:5: Foo.hello/0 " - " TODO: Warning format - " warning: variable "foobar" does not exist and is being expanded to "foobar()", please use parentheses to remove the ambiguity or change the variable name - let l:pattern = '\v\(([^\)]+Error)\) ([^:]+):([^:]+): (.+)$' + " warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore) + " │ + " 17 │ b = another_missing_var + " │ ~ + " │ + " └─ lib/foo.ex:17:5: Foo.hello/0 + " + " error: undefined variable "another_missing_var" + " │ + " 17 │ b = another_missing_var + " │ ^^^^^^^^^^^^^^^^^^^ + " │ + " └─ lib/foo.ex:17:9: Foo.hello/0 + " + " error: undefined variable "missing_var" + " │ + " 16 │ a = missing_var + " │ ^^^^^^^^^^^ + " │ + " └─ lib/foo.ex:16:9: Foo.hello/0 + " + " + " == Compilation error in file lib/foo.ex == + " ** (CompileError) lib/foo.ex: cannot compile module Foo (errors have been logged) + " + " NOTE: The line with the error class is not captured because it always + " seems to be either a `CompileError` or a `SyntaxError`, with a generic + " message. + " + let l:pattern_text = '\v^ (warning|error): (.+)$' + let l:pattern_file = '\v^ └─ ([^ :]+):([0-9]+):([0-9]+)?' + + let l:match_texts = ale#util#GetMatches(a:lines, l:pattern_text) + let l:match_files = ale#util#GetMatches(a:lines, l:pattern_file) + let l:output = [] + let l:index = 0 + + " Always add the full output as detail + let l:detail = join(a:lines, "\n") - for l:match in ale#util#GetMatches(a:lines, l:pattern) - let l:type = 'E' - let l:text = l:match[4] + for l:match_text in l:match_texts + let l:type = toupper(l:match_text[1][0]) + let l:match_file = get(l:match_files, l:index, []) + + if !ale#path#IsBufferPath(a:buffer, get(l:match_file, 1)) + continue + endif call add(l:output, { \ 'bufnr': a:buffer, - \ 'lnum': l:match[3] + 0, - \ 'col': 0, \ 'type': l:type, - \ 'text': l:text, + \ 'detail': l:detail, + \ 'text': get(l:match_text, 2, v:null), + \ 'lnum': str2nr(get(l:match_file, 2, v:null)), + \ 'col': str2nr(get(l:match_file, 3, v:null)), \}) + + let l:index += 1 endfor return l:output endfunction -function! ale_linters#elixir#mix#GetCommand(buffer) abort - let l:temp_dir = ale#command#CreateDirectory(a:buffer) - - return ale#Env('MIX_BUILD_PATH', l:temp_dir) . 'mix compile %s' -endfunction - call ale#linter#Define('elixir', { \ 'name': 'mix', \ 'executable': 'mix', \ 'cwd': function('ale#handlers#elixir#FindMixProjectRoot'), -\ 'command': function('ale_linters#elixir#mix#GetCommand'), +\ 'command': '%e compile', +\ 'output_stream': 'stderr', \ 'callback': 'ale_linters#elixir#mix#Handle', \ 'lint_file': 1, \}) diff --git a/test/handler/test_mix_handler.vader b/test/handler/test_mix_handler.vader index fc04f8f731..ba7e07d37d 100644 --- a/test/handler/test_mix_handler.vader +++ b/test/handler/test_mix_handler.vader @@ -1,20 +1,85 @@ Before: runtime ale_linters/elixir/mix.vim + call ale#test#SetFilename('/testplugin/lib/foo.ex') After: call ale#linter#Reset() Execute(The mix handler should parse lines correctly): + let input_lines = [ + \ ' warning: variable "a" is unused (if the variable is not meant to be used, prefix it with an underscore)', + \ ' │', + \ ' 16 │ a = missing_var', + \ ' │ ~', + \ ' │', + \ ' └─ /testplugin/lib/foo.ex:16:5: Foo.hello/0', + \ '', + \ ' warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)', + \ ' │', + \ ' 17 │ b = another_missing_var', + \ ' │ ~', + \ ' │', + \ ' └─ /testplugin/lib/foo.ex:17:5: Foo.hello/0', + \ '', + \ ' error: undefined variable "another_missing_var"', + \ ' │', + \ ' 17 │ b = another_missing_var', + \ ' │ ^^^^^^^^^^^^^^^^^^^', + \ ' │', + \ ' └─ /testplugin/lib/foo.ex:17:9: Foo.hello/0', + \ '', + \ ' error: undefined variable "missing_var"', + \ ' │', + \ ' 16 │ a = missing_var', + \ ' │ ^^^^^^^^^^^', + \ ' │', + \ ' └─ /testplugin/lib/foo.ex:16:9: Foo.hello/0', + \ '', + \ ' error: undefined variable "unrelated_var"', + \ ' │', + \ ' 16 │ a = unrelated_var', + \ ' │ ^^^^^^^^^^^', + \ ' │', + \ ' └─ /testplugin/lib/unrelated.ex:16:9: Foo.hello/0', + \ '', + \ '', + \ '== Compilation error in file /testplugin/lib/foo.ex ==', + \ '** (CompileError) /testplugin/lib/foo.ex: cannot compile module Foo (errors have been logged)', + \ ] + AssertEqual \ [ \ { - \ 'bufnr': 347, - \ 'lnum': 87, - \ 'col': 0, - \ 'text': 'undefined function update_in/4', + \ 'bufnr': bufnr(''), + \ 'lnum': 16, + \ 'col': 5, + \ 'type': 'W', + \ 'text': 'variable "a" is unused (if the variable is not meant to be used, prefix it with an underscore)', + \ 'detail': join(input_lines, "\n"), + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'lnum': 17, + \ 'col': 5, + \ 'type': 'W', + \ 'text': 'variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)', + \ 'detail': join(input_lines, "\n"), + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'lnum': 17, + \ 'col': 9, + \ 'type': 'E', + \ 'text': 'undefined variable "another_missing_var"', + \ 'detail': join(input_lines, "\n"), + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'lnum': 16, + \ 'col': 9, \ 'type': 'E', + \ 'text': 'undefined variable "missing_var"', + \ 'detail': join(input_lines, "\n"), \ }, \ ], - \ ale_linters#elixir#mix#Handle(347, [ - \ '** (CompileError) apps/sim/lib/sim/server.ex:87: undefined function update_in/4' - \ ]) + \ ale_linters#elixir#mix#Handle(bufnr(''), input_lines) diff --git a/test/linter/test_elixir_mix.vader b/test/linter/test_elixir_mix.vader index a04bee552e..825f6436db 100644 --- a/test/linter/test_elixir_mix.vader +++ b/test/linter/test_elixir_mix.vader @@ -1,19 +1,16 @@ Before: call ale#assert#SetUpLinterTest('elixir', 'mix') call ale#test#SetFilename('../test-files/elixir/mix_project/lib/app.ex') - let g:env_prefix = ale#Env('MIX_BUILD_PATH', 'TEMP_DIR') After: - unlet! g:env_prefix - call ale#assert#TearDownLinterTest() Execute(The default mix command should be correct): AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/mix_project') - AssertLinter 'mix', g:env_prefix . 'mix compile %s' + AssertLinter 'mix', ale#Escape('mix') . ' compile' Execute(Build mix commands with an umbrella root): call ale#test#SetFilename('../test-files/elixir/umbrella_project/apps/mix_project/lib/app.ex') AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') - AssertLinter 'mix', g:env_prefix . 'mix compile %s' + AssertLinter 'mix', ale#Escape('mix') . ' compile'