diff --git a/README.md b/README.md index 5fcca8c7..39d8f501 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ formatting. | reStructuredText | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [rstcheck](https://github.com/myint/rstcheck), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | | Re:VIEW | [redpen](http://redpen.cc/) | | RPM spec | [rpmlint](https://github.com/rpm-software-management/rpmlint) (disabled by default; see `:help ale-integration-spec`) | -| Ruby | [brakeman](http://brakemanscanner.org/) !!, [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) !!, [reek](https://github.com/troessner/reek), [rubocop](https://github.com/bbatsov/rubocop), [ruby](https://www.ruby-lang.org), [rufo](https://github.com/ruby-formatter/rufo), [solargraph](https://solargraph.org) | +| Ruby | [brakeman](http://brakemanscanner.org/) !!, [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) !!, [reek](https://github.com/troessner/reek), [rubocop](https://github.com/bbatsov/rubocop), [ruby](https://www.ruby-lang.org), [rufo](https://github.com/ruby-formatter/rufo), [solargraph](https://solargraph.org), [standardrb](https://github.com/testdouble/standard) | | Rust | [cargo](https://github.com/rust-lang/cargo) !! (see `:help ale-integration-rust` for configuration instructions), [rls](https://github.com/rust-lang-nursery/rls), [rustc](https://www.rust-lang.org/), [rustfmt](https://github.com/rust-lang-nursery/rustfmt) | | SASS | [sass-lint](https://www.npmjs.com/package/sass-lint), [stylelint](https://github.com/stylelint/stylelint) | | SCSS | [prettier](https://github.com/prettier/prettier), [sass-lint](https://www.npmjs.com/package/sass-lint), [scss-lint](https://github.com/brigade/scss-lint), [stylelint](https://github.com/stylelint/stylelint) | diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 45218394..790ca82c 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -13,36 +13,6 @@ function! ale_linters#ruby#rubocop#GetCommand(buffer) abort \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) endfunction -function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort - try - let l:errors = json_decode(a:lines[0]) - catch - return [] - endtry - - if !has_key(l:errors, 'summary') - \|| l:errors['summary']['offense_count'] == 0 - \|| empty(l:errors['files']) - return [] - endif - - let l:output = [] - - for l:error in l:errors['files'][0]['offenses'] - let l:start_col = l:error['location']['column'] + 0 - call add(l:output, { - \ 'lnum': l:error['location']['line'] + 0, - \ 'col': l:start_col, - \ 'end_col': l:start_col + l:error['location']['length'] - 1, - \ 'code': l:error['cop_name'], - \ 'text': l:error['message'], - \ 'type': ale_linters#ruby#rubocop#GetType(l:error['severity']), - \}) - endfor - - return l:output -endfunction - function! ale_linters#ruby#rubocop#GetType(severity) abort if a:severity is? 'convention' \|| a:severity is? 'warning' @@ -57,5 +27,5 @@ call ale#linter#Define('ruby', { \ 'name': 'rubocop', \ 'executable_callback': ale#VarFunc('ruby_rubocop_executable'), \ 'command_callback': 'ale_linters#ruby#rubocop#GetCommand', -\ 'callback': 'ale_linters#ruby#rubocop#Handle', +\ 'callback': 'ale#ruby#HandleRubocopOutput', \}) diff --git a/ale_linters/ruby/standardrb.vim b/ale_linters/ruby/standardrb.vim new file mode 100644 index 00000000..95e10934 --- /dev/null +++ b/ale_linters/ruby/standardrb.vim @@ -0,0 +1,23 @@ +" Author: Justin Searls https://github.com/searls, ynonp - https://github.com/ynonp, Eddie Lebow https://github.com/elebow +" based on the ale rubocop linter +" Description: StandardRB - Ruby Style Guide, with linter & automatic code fixer + +call ale#Set('ruby_standardrb_executable', 'standardrb') +call ale#Set('ruby_standardrb_options', '') + +function! ale_linters#ruby#standardrb#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_standardrb_executable') + + return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb') + \ . ' --format json --force-exclusion ' + \ . ale#Var(a:buffer, 'ruby_standardrb_options') + \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) +endfunction + +" standardrb is based on RuboCop so the callback is the same +call ale#linter#Define('ruby', { +\ 'name': 'standardrb', +\ 'executable_callback': ale#VarFunc('ruby_standardrb_executable'), +\ 'command_callback': 'ale_linters#ruby#standardrb#GetCommand', +\ 'callback': 'ale#ruby#HandleRubocopOutput', +\}) diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index 75fd1508..6a8fd382 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -115,6 +115,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['javascript'], \ 'description': 'Fix JavaScript files using standard --fix', \ }, +\ 'standardrb': { +\ 'function': 'ale#fixers#standardrb#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with standardrb --fix', +\ }, \ 'stylelint': { \ 'function': 'ale#fixers#stylelint#Fix', \ 'suggested_filetypes': ['css', 'sass', 'scss', 'stylus'], diff --git a/autoload/ale/fixers/standardrb.vim b/autoload/ale/fixers/standardrb.vim new file mode 100644 index 00000000..fab1e2bc --- /dev/null +++ b/autoload/ale/fixers/standardrb.vim @@ -0,0 +1,23 @@ +" Author: Justin Searls - https://github.com/searls +" Description: Fix Ruby files with StandardRB. + +call ale#Set('ruby_standardrb_options', '') +call ale#Set('ruby_standardrb_executable', 'standardrb') + +function! ale#fixers#standardrb#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_standardrb_executable') + let l:config = ale#path#FindNearestFile(a:buffer, '.standard.yml') + let l:options = ale#Var(a:buffer, 'ruby_standardrb_options') + + return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb') + \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --fix --force-exclusion %t' +endfunction + +function! ale#fixers#standardrb#Fix(buffer) abort + return { + \ 'command': ale#fixers#standardrb#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim index 5f0aa50d..3d9c5a51 100644 --- a/autoload/ale/ruby.vim +++ b/autoload/ale/ruby.vim @@ -42,3 +42,35 @@ function! ale#ruby#FindProjectRoot(buffer) abort return '' endfunction + +" Handle output from rubocop and linters that depend on it (e.b. standardrb) +function! ale#ruby#HandleRubocopOutput(buffer, lines) abort + try + let l:errors = json_decode(a:lines[0]) + catch + return [] + endtry + + if !has_key(l:errors, 'summary') + \|| l:errors['summary']['offense_count'] == 0 + \|| empty(l:errors['files']) + return [] + endif + + let l:output = [] + + for l:error in l:errors['files'][0]['offenses'] + let l:start_col = l:error['location']['column'] + 0 + call add(l:output, { + \ 'lnum': l:error['location']['line'] + 0, + \ 'col': l:start_col, + \ 'end_col': l:start_col + l:error['location']['length'] - 1, + \ 'code': l:error['cop_name'], + \ 'text': l:error['message'], + \ 'type': ale_linters#ruby#rubocop#GetType(l:error['severity']), + \}) + endfor + + return l:output +endfunction + diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt index f8a41999..bf971e7c 100644 --- a/doc/ale-ruby.txt +++ b/doc/ale-ruby.txt @@ -129,5 +129,27 @@ g:ale_ruby_solargraph_executable *g:ale_ruby_solargraph_executable* from binstubs or a bundle. +=============================================================================== +standardrb *ale-ruby-standardrb* + +g:ale_ruby_standardrb_executable *g:ale_ruby_standardrb_executable* + *b:ale_ruby_standardrb_executable* + Type: String + Default: `'standardrb'` + + Override the invoked standardrb binary. Set this to `'bundle'` to invoke + `'bundle` `exec` standardrb'. + + +g:ale_ruby_standardrb_options *g:ale_ruby_standardrb_options* + *b:ale_ruby_standardrb_options* + Type: |String| + Default: `''` + + This variable can be change to modify flags given to standardrb. + + +=============================================================================== + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale.txt b/doc/ale.txt index 87d4e017..0b127725 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -285,6 +285,7 @@ CONTENTS *ale-contents* ruby................................|ale-ruby-ruby| rufo................................|ale-ruby-rufo| solargraph..........................|ale-ruby-solargraph| + standardrb..........................|ale-ruby-standardrb| rust..................................|ale-rust-options| cargo...............................|ale-rust-cargo| rls.................................|ale-rust-rls| @@ -486,7 +487,7 @@ Notes: * reStructuredText: `alex`!!, `proselint`, `redpen`, `rstcheck`, `vale`, `write-good` * Re:VIEW: `redpen` * RPM spec: `rpmlint` -* Ruby: `brakeman`, `rails_best_practices`!!, `reek`, `rubocop`, `ruby`, `rufo`, `solargraph` +* Ruby: `brakeman`, `rails_best_practices`!!, `reek`, `rubocop`, `ruby`, `rufo`, `solargraph`, `standardrb` * Rust: `cargo`!!, `rls`, `rustc` (see |ale-integration-rust|), `rustfmt` * SASS: `sass-lint`, `stylelint` * SCSS: `prettier`, `sass-lint`, `scss-lint`, `stylelint` diff --git a/test/command_callback/ruby_paths/with_config/.standard.yml b/test/command_callback/ruby_paths/with_config/.standard.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/command_callback/test_standardrb_command_callback.vader b/test/command_callback/test_standardrb_command_callback.vader new file mode 100644 index 00000000..7bc1c976 --- /dev/null +++ b/test/command_callback/test_standardrb_command_callback.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'standardrb') + call ale#test#SetFilename('dummy.rb') + + let g:ale_ruby_standardrb_executable = 'standardrb' + let g:ale_ruby_standardrb_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to standardrb): + AssertLinter 'standardrb', ale#Escape('standardrb') + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + +Execute(Should be able to set a custom executable): + let g:ale_ruby_standardrb_executable = 'bin/standardrb' + + AssertLinter 'bin/standardrb' , ale#Escape('bin/standardrb') + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + +Execute(Setting bundle appends 'exec standardrb'): + let g:ale_ruby_standardrb_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec standardrb' + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) diff --git a/test/fixers/test_standardrb_fixer_callback.vader b/test/fixers/test_standardrb_fixer_callback.vader new file mode 100644 index 00000000..99234b79 --- /dev/null +++ b/test/fixers/test_standardrb_fixer_callback.vader @@ -0,0 +1,54 @@ +Before: + Save g:ale_ruby_standardrb_executable + Save g:ale_ruby_standardrb_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_standardrb_executable = 'xxxinvalid' + let g:ale_ruby_standardrb_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The standardrb callback should return the correct default values): + call ale#test#SetFilename('ruby_paths/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --fix --force-exclusion %t', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) + +Execute(The standardrb callback should include configuration files): + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.standard.yml')) + \ . ' --fix --force-exclusion %t', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) + +Execute(The standardrb callback should include custom rubocop options): + let g:ale_ruby_standardrb_options = '--except Lint/Debugger' + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.standard.yml')) + \ . ' --except Lint/Debugger' + \ . ' --fix --force-exclusion %t', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) diff --git a/test/handler/test_rubocop_handler.vader b/test/handler/test_rubocop_handler.vader index ef0137d6..d7868f26 100644 --- a/test/handler/test_rubocop_handler.vader +++ b/test/handler/test_rubocop_handler.vader @@ -41,21 +41,21 @@ Execute(The rubocop handler should parse lines correctly): \ 'type': 'E', \ }, \ ], - \ ale_linters#ruby#rubocop#Handle(347, [ + \ ale#ruby#HandleRubocopOutput(347, [ \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[{"path":"my_great_file.rb","offenses":[{"severity":"convention","message":"Prefer single-quoted strings...","cop_name":"Style/SomeCop","corrected":false,"location":{"line":83,"column":29,"length":7}},{"severity":"fatal","message":"Some error","cop_name":"Style/SomeOtherCop","corrected":false,"location":{"line":12,"column":2,"length":1}},{"severity":"warning","message":"Regular warning","cop_name":"Style/WarningCop","corrected":false,"location":{"line":10,"column":5,"length":8}},{"severity":"error","message":"Another error","cop_name":"Style/SpaceBeforeBlockBraces","corrected":false,"location":{"line":11,"column":1,"length":1}}]}],"summary":{"offense_count":4,"target_file_count":1,"inspected_file_count":1}}' \ ]) Execute(The rubocop handler should handle when files are checked and no offenses are found): AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, [ + \ ale#ruby#HandleRubocopOutput(347, [ \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[{"path":"my_great_file.rb","offenses":[]}],"summary":{"offense_count":0,"target_file_count":1,"inspected_file_count":1}}' \ ]) Execute(The rubocop handler should handle when no files are checked): AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, [ + \ ale#ruby#HandleRubocopOutput(347, [ \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[],"summary":{"offense_count":0,"target_file_count":0,"inspected_file_count":0}}' \ ]) @@ -66,11 +66,11 @@ Execute(The rubocop handler should handle output without any errors): AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, g:lines) + \ ale#ruby#HandleRubocopOutput(347, g:lines) \ AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, ['{}']) + \ ale#ruby#HandleRubocopOutput(347, ['{}']) AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, []) + \ ale#ruby#HandleRubocopOutput(347, [])