1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
" Author: Kevin Locke <kevin@kevinlocke.name>
" Description: pylama for python files
call ale#Set('python_pylama_executable', 'pylama')
call ale#Set('python_pylama_options', '')
call ale#Set('python_pylama_use_global', get(g:, 'ale_use_global_executables', 0))
call ale#Set('python_pylama_auto_pipenv', 0)
call ale#Set('python_pylama_auto_poetry', 0)
call ale#Set('python_pylama_auto_uv', 0)
call ale#Set('python_pylama_change_directory', 1)
function! ale_linters#python#pylama#GetExecutable(buffer) abort
if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pylama_auto_pipenv'))
\ && ale#python#PipenvPresent(a:buffer)
return 'pipenv'
endif
if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pylama_auto_poetry'))
\ && ale#python#PoetryPresent(a:buffer)
return 'poetry'
endif
if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pylama_auto_uv'))
\ && ale#python#UvPresent(a:buffer)
return 'uv'
endif
return ale#python#FindExecutable(a:buffer, 'python_pylama', ['pylama'])
endfunction
function! ale_linters#python#pylama#RunWithVersionCheck(buffer) abort
let l:executable = ale_linters#python#pylama#GetExecutable(a:buffer)
let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$'
\ ? ' run pylama'
\ : ''
let l:command = ale#Escape(l:executable) . l:exec_args . ' --version'
return ale#semver#RunWithVersionCheck(
\ a:buffer,
\ l:executable,
\ l:command,
\ function('ale_linters#python#pylama#GetCommand'),
\)
endfunction
function! ale_linters#python#pylama#GetCwd(buffer) abort
if ale#Var(a:buffer, 'python_pylama_change_directory')
" Pylama loads its configuration from the current directory only, and
" applies file masks using paths relative to the current directory.
" Run from project root, if found, otherwise buffer dir.
let l:project_root = ale#python#FindProjectRoot(a:buffer)
return !empty(l:project_root) ? l:project_root : '%s:h'
endif
return ''
endfunction
function! ale_linters#python#pylama#GetCommand(buffer, version) abort
let l:executable = ale_linters#python#pylama#GetExecutable(a:buffer)
let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$'
\ ? ' run pylama'
\ : ''
" json format is added in version 8.1.4
" https://github.com/klen/pylama/blob/develop/Changelog
let l:format_json_args = ale#semver#GTE(a:version, [8, 1, 4])
\ ? ' --format json'
\ : ''
" Note: Using %t to lint changes would be preferable, but many pylama
" checks use surrounding paths (e.g. C0103 module name, E0402 relative
" import beyond top, etc.). Neither is ideal.
return ale#Escape(l:executable) . l:exec_args
\ . ale#Pad(ale#Var(a:buffer, 'python_pylama_options'))
\ . l:format_json_args
\ . ' %s'
endfunction
function! ale_linters#python#pylama#Handle(buffer, version, lines) abort
if empty(a:lines)
return []
endif
let l:output = ale#python#HandleTraceback(a:lines, 1)
" First letter of error code is a pylint-compatible message type
" http://pylint.pycqa.org/en/latest/user_guide/output.html#source-code-analysis-section
" D is for Documentation (pydocstyle)
let l:pylint_type_to_ale_type = {
\ 'I': 'I',
\ 'R': 'W',
\ 'C': 'W',
\ 'W': 'W',
\ 'E': 'E',
\ 'F': 'E',
\ 'D': 'W',
\}
let l:pylint_type_to_ale_sub_type = {
\ 'R': 'style',
\ 'C': 'style',
\ 'D': 'style',
\}
if ale#semver#GTE(a:version, [8, 1, 4])
try
let l:errors = json_decode(join(a:lines, ''))
catch
return l:output
endtry
if empty(l:errors)
return l:output
endif
for l:error in l:errors
call add(l:output, {
\ 'lnum': l:error['lnum'],
\ 'col': l:error['col'],
\ 'code': l:error['number'],
\ 'type': get(l:pylint_type_to_ale_type, l:error['etype'], 'W'),
\ 'sub_type': get(l:pylint_type_to_ale_sub_type, l:error['etype'], ''),
\ 'text': printf('%s [%s]', l:error['message'], l:error['source']),
\})
endfor
else
let l:pattern = '\v^.{-}:([0-9]+):([0-9]+): +%(([A-Z][0-9]+):? +)?(.*)$'
for l:match in ale#util#GetMatches(a:lines, l:pattern)
call add(l:output, {
\ 'lnum': str2nr(l:match[1]),
\ 'col': str2nr(l:match[2]),
\ 'code': l:match[3],
\ 'type': get(l:pylint_type_to_ale_type, l:match[3][0], 'W'),
\ 'sub_type': get(l:pylint_type_to_ale_sub_type, l:match[3][0], ''),
\ 'text': l:match[4],
\})
endfor
endif
return l:output
endfunction
call ale#linter#Define('python', {
\ 'name': 'pylama',
\ 'executable': function('ale_linters#python#pylama#GetExecutable'),
\ 'cwd': function('ale_linters#python#pylama#GetCwd'),
\ 'command': function('ale_linters#python#pylama#RunWithVersionCheck'),
\ 'callback': {buffer, lines -> ale#semver#RunWithVersionCheck(
\ buffer,
\ ale_linters#python#pylama#GetExecutable(buffer),
\ '%e --version',
\ {buffer, version -> ale_linters#python#pylama#Handle(
\ buffer,
\ l:version,
\ lines)},
\ )},
\ 'lint_file': 1,
\})
|