diff --git a/README.md b/README.md index 9eebf5d1..17b243d0 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ formatting. | proto | [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) | | Pug | [pug-lint](https://github.com/pugjs/pug-lint) | | Puppet | [languageserver](https://github.com/lingua-pupuli/puppet-editor-services), [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) | -| Python | [autopep8](https://github.com/hhatto/autopep8), [black](https://github.com/ambv/black), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [prospector](https://github.com/PyCQA/prospector), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pydocstyle](https://www.pydocstyle.org/), [pyls](https://github.com/palantir/python-language-server), [pyre](https://github.com/facebook/pyre-check), [pylint](https://www.pylint.org/) !!, [vulture](https://github.com/jendrikseipp/vulture) !!, [yapf](https://github.com/google/yapf) | +| Python | [autopep8](https://github.com/hhatto/autopep8), [bandit](https://github.com/PyCQA/bandit), [black](https://github.com/ambv/black), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [prospector](https://github.com/PyCQA/prospector), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pydocstyle](https://www.pydocstyle.org/), [pyls](https://github.com/palantir/python-language-server), [pyre](https://github.com/facebook/pyre-check), [pylint](https://www.pylint.org/) !!, [vulture](https://github.com/jendrikseipp/vulture) !!, [yapf](https://github.com/google/yapf) | | QML | [qmlfmt](https://github.com/jesperhh/qmlfmt), [qmllint](https://github.com/qt/qtdeclarative/tree/5.11/tools/qmllint) | | R | [lintr](https://github.com/jimhester/lintr) | | Racket | [raco](https://docs.racket-lang.org/raco/) | diff --git a/ale_linters/python/bandit.vim b/ale_linters/python/bandit.vim new file mode 100644 index 00000000..1b5a84a4 --- /dev/null +++ b/ale_linters/python/bandit.vim @@ -0,0 +1,58 @@ +" Author: Martino Pilia +" Description: bandit linting for python files + +call ale#Set('python_bandit_executable', 'bandit') +call ale#Set('python_bandit_options', '') +call ale#Set('python_bandit_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_bandit_auto_pipenv', 0) + +function! ale_linters#python#bandit#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || + \ ale#Var(a:buffer, 'python_bandit_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_bandit', ['bandit']) +endfunction + +function! ale_linters#python#bandit#GetCommand(buffer) abort + let l:executable = ale_linters#python#bandit#GetExecutable(a:buffer) + let l:flags = ' --format custom' + \ . ' --msg-template "{line}:{test_id}:{severity}:{msg}" ' + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run bandit' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . l:flags + \ . ale#Pad(ale#Var(a:buffer, 'python_bandit_options')) + \ . ' -' +endfunction + +function! ale_linters#python#bandit#Handle(buffer, lines) abort + " Custom format defined in GetCommand via --msg-template + let l:pattern = '\v^([0-9]+):(B[0-9]+):([A-Z]+):(.*)$' + let l:severity = {'LOW': 'I', 'MEDIUM': 'W', 'HIGH': 'E'} + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': str2nr(l:match[1]), + \ 'code': l:match[2], + \ 'type': l:severity[l:match[3]], + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'bandit', +\ 'executable_callback': 'ale_linters#python#bandit#GetExecutable', +\ 'command_callback': 'ale_linters#python#bandit#GetCommand', +\ 'callback': 'ale_linters#python#bandit#Handle', +\}) diff --git a/doc/ale-python.txt b/doc/ale-python.txt index 3d355bc6..175216f6 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -65,6 +65,45 @@ g:ale_python_autopep8_use_global *g:ale_python_autopep8_use_global* See |ale-integrations-local-executables| +=============================================================================== +bandit *ale-python-bandit* + +g:ale_python_bandit_executable *g:ale_python_bandit_executable* + *b:ale_python_bandit_executable* + Type: |String| + Default: `'bandit'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `bandit'`. + + +g:ale_python_bandit_options *g:ale_python_bandit_options* + *b:ale_python_bandit_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + bandit invocation. + + +g:ale_python_bandit_use_global *g:ale_python_bandit_use_global* + *b:ale_python_bandit_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +g:ale_python_bandit_auto_pipenv *g:ale_python_bandit_auto_pipenv* + *b:ale_python_bandit_auto_pipenv* + Type: |Number| + Default: `0` + + Detect whether the file is inside a pipenv, and set the executable to `pipenv` + if true. This is overridden by a manually-set executable. + + =============================================================================== black *ale-python-black* diff --git a/doc/ale.txt b/doc/ale.txt index e0de8d1e..580effc7 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -257,6 +257,7 @@ CONTENTS *ale-contents* cython..............................|ale-pyrex-cython| python................................|ale-python-options| autopep8............................|ale-python-autopep8| + bandit..............................|ale-python-bandit| black...............................|ale-python-black| flake8..............................|ale-python-flake8| isort...............................|ale-python-isort| @@ -484,7 +485,7 @@ Notes: * proto: `protoc-gen-lint` * Pug: `pug-lint` * Puppet: `languageserver`, `puppet`, `puppet-lint` -* Python: `autopep8`, `black`, `flake8`, `isort`, `mypy`, `prospector`, `pycodestyle`, `pydocstyle`, `pyls`, `pyre`, `pylint`!!, `vulture`!!, `yapf` +* Python: `autopep8`, `bandit`, `black`, `flake8`, `isort`, `mypy`, `prospector`, `pycodestyle`, `pydocstyle`, `pyls`, `pyre`, `pylint`!!, `vulture`!!, `yapf` * QML: `qmlfmt`, `qmllint` * R: `lintr` * Racket: `raco` diff --git a/test/command_callback/test_bandit_command_callback.vader b/test/command_callback/test_bandit_command_callback.vader new file mode 100644 index 00000000..5d1e6fd3 --- /dev/null +++ b/test/command_callback/test_bandit_command_callback.vader @@ -0,0 +1,49 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'bandit') + let b:bandit_flags = ' --format custom ' + \ . '--msg-template "{line}:{test_id}:{severity}:{msg}" ' + +After: + call ale#assert#TearDownLinterTest() + unlet! b:bandit_flags + +Execute(The bandit command callback should return default string): + AssertLinter 'bandit', + \ ale#Escape('bandit') + \ . b:bandit_flags + \ . ' -' + +Execute(The bandit command callback should allow options): + let g:ale_python_bandit_options = '--configfile bandit.yaml' + + AssertLinter 'bandit', + \ ale#Escape('bandit') + \ . b:bandit_flags + \ . ' --configfile bandit.yaml -' + +Execute(The bandit executable should be configurable): + let g:ale_python_bandit_executable = '~/.local/bin/bandit' + + AssertLinter '~/.local/bin/bandit', + \ ale#Escape('~/.local/bin/bandit') + \ . b:bandit_flags + \ . ' -' + +Execute(Setting executable to 'pipenv' appends 'run bandit'): + let g:ale_python_bandit_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(Pipenv is detected when python_bandit_auto_pipenv is set): + let g:ale_python_bandit_auto_pipenv = 1 + call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' diff --git a/test/handler/test_bandit_handler.vader b/test/handler/test_bandit_handler.vader new file mode 100644 index 00000000..a2793a46 --- /dev/null +++ b/test/handler/test_bandit_handler.vader @@ -0,0 +1,42 @@ +Before: + runtime ale_linters/python/bandit.vim + +After: + call ale#linter#Reset() + +Execute(The bandit handler for Python should parse input correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 0, + \ 'lnum': 2, + \ 'code': 'B404', + \ 'type': 'I', + \ 'text': 'Consider possible security implications associated with subprocess module.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 4, + \ 'code': 'B305', + \ 'type': 'W', + \ 'text': 'Use of insecure cipher mode cryptography.hazmat.primitives.ciphers.modes.ECB.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 6, + \ 'code': 'B609', + \ 'type': 'E', + \ 'text': 'Possible wildcard injection in call: subprocess.Popen', + \ }, + \ ], + \ ale_linters#python#bandit#Handle(0, [ + \ '[main] INFO profile include tests: None', + \ '[main] INFO profile exclude tests: None', + \ '[main] INFO cli include tests: None', + \ '[main] INFO cli exclude tests: None', + \ '[main] INFO running on Python 3.7.2', + \ '[node_visitor] INFO Unable to find qualified name for module: ', + \ '2:B404:LOW:Consider possible security implications associated with subprocess module.', + \ '4:B305:MEDIUM:Use of insecure cipher mode cryptography.hazmat.primitives.ciphers.modes.ECB.', + \ '6:B609:HIGH:Possible wildcard injection in call: subprocess.Popen', + \ ])