From 2cfa09e02d65cd06649fb1ae5f988b7a110a124d Mon Sep 17 00:00:00 2001 From: Justin Searls Date: Mon, 10 Dec 2018 16:02:32 -0500 Subject: Adds standardrb linter (#2133) See: https://github.com/testdouble/standard StandardRB is to RuboCop what StandardJS is to ESLint. This commit naively copies the RuboCop linter and fixer to point at the standardrb executable. Any other adjustments are very minor (the only I can think of is that standardrb takes a `--fix` option instead of `--auto-correct`). This raises a confusing point to me as both developer and a user: since ale enables all linters by default, won't this run both RuboCop and StandardRB (the results of which will almost always be in conflict with one another)? How does ale already solve for this for the similar case of StandardJS and ESLint? --- README.md | 2 +- ale_linters/ruby/rubocop.vim | 32 +------------ ale_linters/ruby/standardrb.vim | 23 +++++++++ autoload/ale/fix/registry.vim | 5 ++ autoload/ale/fixers/standardrb.vim | 23 +++++++++ autoload/ale/ruby.vim | 32 +++++++++++++ doc/ale-ruby.txt | 22 +++++++++ doc/ale.txt | 3 +- .../ruby_paths/with_config/.standard.yml | 0 .../test_standardrb_command_callback.vader | 29 ++++++++++++ test/fixers/test_standardrb_fixer_callback.vader | 54 ++++++++++++++++++++++ test/handler/test_rubocop_handler.vader | 12 ++--- 12 files changed, 198 insertions(+), 39 deletions(-) create mode 100644 ale_linters/ruby/standardrb.vim create mode 100644 autoload/ale/fixers/standardrb.vim create mode 100644 test/command_callback/ruby_paths/with_config/.standard.yml create mode 100644 test/command_callback/test_standardrb_command_callback.vader create mode 100644 test/fixers/test_standardrb_fixer_callback.vader 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, []) -- cgit v1.2.3