From 9092af9ad6a5c93a759be571323ea1d0cafa8d6e Mon Sep 17 00:00:00 2001 From: ymap Date: Wed, 13 Sep 2023 23:53:24 +0900 Subject: Add support for Packwerk (#4594) Packwerk (https://github.com/Shopify/packwerk) is a Ruby gem used to enforce boundaries and modularize Rails applications. --- ale_linters/ruby/packwerk.vim | 55 +++++++++++++++++++++++++++++++ doc/ale-ruby.txt | 20 +++++++++++ doc/ale-supported-languages-and-tools.txt | 1 + doc/ale.txt | 1 + supported-tools.md | 1 + test/handler/test_packwerk_handler.vader | 29 ++++++++++++++++ test/linter/test_packwerk.vader | 38 +++++++++++++++++++++ 7 files changed, 145 insertions(+) create mode 100644 ale_linters/ruby/packwerk.vim create mode 100644 test/handler/test_packwerk_handler.vader create mode 100644 test/linter/test_packwerk.vader diff --git a/ale_linters/ruby/packwerk.vim b/ale_linters/ruby/packwerk.vim new file mode 100644 index 00000000..4014b2da --- /dev/null +++ b/ale_linters/ruby/packwerk.vim @@ -0,0 +1,55 @@ +" Author: ymap - https://github.com/ymap +" Description: Packwerk, a static analyzer used to enforce boundaries and modularize Rails applications. + +call ale#Set('ruby_packwerk_executable', 'packwerk') +call ale#Set('ruby_packwerk_options', '') + +function! ale_linters#ruby#packwerk#Handle(buffer, lines) abort + let l:pattern = '\v^[^:]+:(\d+):(\d+)$' + let l:index = 0 + let l:output = [] + + while l:index < len(a:lines) - 1 + let l:cleaned_line = substitute(a:lines[l:index], '\v\e\[[0-9;]*m', '', 'g') + let l:match = matchlist(l:cleaned_line, l:pattern) + + if len(l:match) > 0 + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': a:lines[l:index + 1], + \}) + endif + + let l:index += 1 + endwhile + + return l:output +endfunction + +function! ale_linters#ruby#packwerk#GetCommand(buffer) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if l:rails_root is? '' + return '' + endif + + let l:executable = ale#Var(a:buffer, 'ruby_packwerk_executable') + let l:sep = has('win32') ? '\' : '/' + let l:abs_path = expand('#' . a:buffer . ':p') + let l:rel_path = substitute(l:abs_path, escape(l:rails_root . l:sep, '\'), '', '') + + return ale#ruby#EscapeExecutable(l:executable, 'packwerk') + \ . ' check' + \ . ale#Pad(ale#Var(a:buffer, 'ruby_packwerk_options')) + \ . ' ' + \ . ale#Escape(rel_path) +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'packwerk', +\ 'executable': {b -> ale#Var(b, 'ruby_packwerk_executable')}, +\ 'command': function('ale_linters#ruby#packwerk#GetCommand'), +\ 'callback': 'ale_linters#ruby#packwerk#Handle', +\ 'lint_file': 1, +\}) diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt index b0773fdd..ff9aa798 100644 --- a/doc/ale-ruby.txt +++ b/doc/ale-ruby.txt @@ -48,6 +48,26 @@ g:ale_ruby_debride_options *g:ale_ruby_debride_options* This variable can be changed to modify flags given to debride. +=============================================================================== +packwerk *ale-ruby-packwerk* + +g:ale_ruby_packwerk_executable *g:ale_ruby_packwerk_executable* + *b:ale_ruby_packwerk_executable* + Type: |String| + Default: `'packwerk'` + + Override the invoked packwerk binary. Set this to `'bundle'` to invoke + `'bundle` `exec` packwerk'. + + +g:ale_ruby_packwerk_options *g:ale_ruby_packwerk_options* + *b:ale_ruby_packwerk_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to packwerk. + + =============================================================================== prettier *ale-ruby-prettier* diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt index ff5a914f..b973b8b4 100644 --- a/doc/ale-supported-languages-and-tools.txt +++ b/doc/ale-supported-languages-and-tools.txt @@ -547,6 +547,7 @@ Notes: * `brakeman`!! * `cspell` * `debride` + * `packwerk`!! * `prettier` * `rails_best_practices`!! * `reek` diff --git a/doc/ale.txt b/doc/ale.txt index ded91aa3..423204a3 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -3324,6 +3324,7 @@ documented in additional help files. brakeman..............................|ale-ruby-brakeman| cspell................................|ale-ruby-cspell| debride...............................|ale-ruby-debride| + packwerk..............................|ale-ruby-packwerk| prettier..............................|ale-ruby-prettier| rails_best_practices..................|ale-ruby-rails_best_practices| reek..................................|ale-ruby-reek| diff --git a/supported-tools.md b/supported-tools.md index 637e771d..05bea334 100644 --- a/supported-tools.md +++ b/supported-tools.md @@ -556,6 +556,7 @@ formatting. * [brakeman](http://brakemanscanner.org/) :floppy_disk: * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) * [debride](https://github.com/seattlerb/debride) + * [packwerk](https://github.com/Shopify/packwerk) :floppy_disk: * [prettier](https://github.com/prettier/plugin-ruby) * [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) :floppy_disk: * [reek](https://github.com/troessner/reek) diff --git a/test/handler/test_packwerk_handler.vader b/test/handler/test_packwerk_handler.vader new file mode 100644 index 00000000..b95b7556 --- /dev/null +++ b/test/handler/test_packwerk_handler.vader @@ -0,0 +1,29 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + cd .. + + runtime ale_linters/ruby/packwerk.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The packwerk handler should parse lines correctly): + call ale#test#SetFilename('test-files/ruby/valid_rails_app/app/models/thing.rb') + + AssertEqual + \ [ + \ { + \ 'lnum': 36, + \ 'col': 100, + \ 'text': 'Dependency violation: ::Edi::Source belongs to ''edi'', but ''billing'' does not specify a dependency on ''edi''.', + \ } + \ ], + \ ale_linters#ruby#packwerk#Handle(bufnr(''), [ + \ '/Users/JaneDoe/src/github.com/sample-project/billing/app/jobs/document_processing_job.rb:36:100', + \ 'Dependency violation: ::Edi::Source belongs to ''edi'', but ''billing'' does not specify a dependency on ''edi''.', + \ 'Are we missing an abstraction?', + \ 'Is the code making the reference, and the referenced constant, in the right packages?', + \ '', + \ 'Inference details: ''Edi::Source'' refers to ::Edi::Source which seems to be defined in edi/app/models/edi/source.rb.', + \ ]) diff --git a/test/linter/test_packwerk.vader b/test/linter/test_packwerk.vader new file mode 100644 index 00000000..9a91fd9e --- /dev/null +++ b/test/linter/test_packwerk.vader @@ -0,0 +1,38 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'packwerk') + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/db/test.rb') + + let g:ale_ruby_packwerk_executable = 'packwerk' + let g:ale_ruby_packwerk_options = '' + + let b:sep = has('win32') ? '\' : '/' + +After: + unlet! b:sep + + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to packwerk): + AssertLinter 'packwerk', ale#Escape('packwerk') + \ . ' check ' + \ . ale#Escape('db' . b:sep . 'test.rb') + +Execute(Should be able to set a custom executable): + let g:ale_ruby_packwerk_executable = 'bin/packwerk' + + AssertLinter 'bin/packwerk', ale#Escape('bin/packwerk') + \ . ' check ' + \ . ale#Escape('db' . b:sep . 'test.rb') + +Execute(Setting bundle appends 'exec packwerk'): + let g:ale_ruby_packwerk_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec packwerk' + \ . ' check ' + \ . ale#Escape('db' . b:sep . 'test.rb') + +Execute(Command callback should be empty when not in a valid Rails app): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/test.rb') + + AssertLinter 'packwerk', '' -- cgit v1.2.3