wfleming/vim-codeclimate

View on GitHub
plugin/codeclimate.vim

Summary

Maintainability
Test Coverage
" Runs the CodeClimate CLI and display the results in the quickfix window
"
" Author:    Will Fleming
" URL:       https://github.com/wfleming/vim-codeclimate
" Version:   0.2.0
" Copyright: Copyright (c) 2016 Will Fleming
" License:   MIT
" ----------------------------------------------------------------------------

if exists('g:loaded_vimcodeclimate') || &cp
  finish
endif
let g:loaded_vimcodeclimate = 1

if !exists('g:vimcodeclimate_analyze_cmd')
  let g:vimcodeclimate_analyze_cmd = 'codeclimate analyze'
endif

let s:BUF_OPT_VAR = 'codeclimateflags'

function! s:AnalyzeProject()
  if s:AnyBufferModified(s:EligibleBuffers())
    call s:ModifiedFilesWarning()
  endif

  call s:RunAnalysis('')
endfunction

function! s:AnalyzeOpenFiles()
  let l:bufs = s:EligibleBuffers()
  if s:AnyBufferModified(l:bufs)
    call s:ModifiedFilesWarning()
  endif

  let l:extraopts = s:BufferAnalysisOpts(l:bufs)
  let l:files = s:BufferNames(l:bufs)
  let l:cli = join(l:extraopts, ' ') . ' ' . join(map(l:files, 'shellescape(v:val)'), ' ')

  call s:RunAnalysis(l:cli)
endfunction

function! s:AnalyzeCurrentFile()
  if &modified
    call s:ModifiedFilesWarning()
  endif

  let l:cli = getbufvar(@%, s:BUF_OPT_VAR) . ' ' . shellescape(@%)

  call s:RunAnalysis(l:cli)
endfunction

function! s:EligibleBuffers()
  let l:bufs = []
  let l:idx = bufnr('^')
  while l:idx <= bufnr('$')
    if '' ==# getbufvar(l:idx, '&buftype') && filereadable(expand('#'.l:idx.':p'))
      call add(l:bufs, l:idx)
    endif
    let l:idx = l:idx + 1
  endwhile
  return l:bufs
endfunction

function! s:BufferAnalysisOpts(buffers)
  let l:opts = []
  for l:idx in a:buffers
    let l:opt = getbufvar(l:idx, s:BUF_OPT_VAR)
    if '' !=# l:opt
      call add(l:opts, l:opt)
    endif
  endfor
  return l:opts
endfunction

function! s:BufferNames(buffers)
  let l:names = []
  let l:cwd = getcwd()
  for l:idx in a:buffers
    let l:fullPath = expand('#'.l:idx.':p')
    if 0 == stridx(l:fullPath, l:cwd)
      let l:relPath = strpart(l:fullPath, strlen(l:cwd) + 1)
      call add(l:names, l:relPath)
    endif
  endfor
  return l:names
endfunction

function! s:AnyBufferModified(buffers)
  for l:idx in a:buffers
    if getbufvar(l:idx, '&mod')
      return 1
    endif
  endfor
  return 0
endfunction

function! s:ModifiedFilesWarning()
  echohl WarningMsg | echo 'CodeClimate analyzes files on disk: unsaved changes will not be analyzed.' | echohl None
endfunction

function! s:RunAnalysis(files)
  echo 'Running codeclimate analyze...'
  let l:analyze_cmd = g:vimcodeclimate_analyze_cmd.' '.a:files
  let l:analyze_output = system(l:analyze_cmd)
  if v:shell_error
    echohl ErrorMsg
    echo 'codeclimate failed: try `codeclimate validate-config` or `codeclimate analyze` in your shell.'
    echo 'failed command: '.l:analyze_cmd
    echohl None
    return
  endif
  let l:issues = s:ParseIssues(l:analyze_output)
  if 0 == len(l:issues)
    echo 'Code Climate found no issues! Well done.'
  else
    call s:ShowIssues(l:issues)
  endif
endfunction

function! s:ParseIssues(text)
  let l:issues = []
  let l:currentFile = ''
  let l:lineNumber = ''
  for l:line in split(a:text, "\n")
    if 0 == stridx(l:line, '== ')
      let l:currentFile = substitute(l:line, '\v^\=\= (.+) \(.+\=\=$', '\1', 'i')
    elseif matchstr(l:line, '\v^\d+(-\d+)?:')
      let l:lineNumber = substitute(l:line, '\v^(\d+)(-\d+)(:.*)', '\1\3', 'g')
      call add(l:issues, l:currentFile.':'.l:lineNumber)
    endif
  endfor
  return l:issues
endfunction

function! s:ShowIssues(issues)
  let l:old_errorformat = &errorformat
  set errorformat=%f:%l:%m
  cexpr a:issues
  copen
  call s:BindQuickShortcuts()
  let &errorformat = l:old_errorformat
endfunction

function! s:BindQuickShortcuts()
  " Shortcuts borrowed from vim-rubocop via Ack.vim -
  " git://github.com/ngmy/vim-rubocop.git
  nnoremap <buffer> <silent> ? :call <SID>QuickHelp()<CR>
  nnoremap <silent> <buffer> q :ccl<CR>
  nnoremap <silent> <buffer> t <C-W><CR><C-W>T
  nnoremap <silent> <buffer> T <C-W><CR><C-W>TgT<C-W><C-W>
  nnoremap <silent> <buffer> o <CR>
  nnoremap <silent> <buffer> go <CR><C-W><C-W>
  nnoremap <silent> <buffer> h <C-W><CR><C-W>K
  nnoremap <silent> <buffer> H <C-W><CR><C-W>K<C-W>b
  nnoremap <silent> <buffer> v <C-W><CR><C-W>H<C-W>b<C-W>J<C-W>t
  nnoremap <silent> <buffer> gv <C-W><CR><C-W>H<C-W>b<C-W>J
endfunction

function! s:QuickHelp()
  execute 'edit' globpath(&rtp, 'doc/codeclimate_quick_help.txt')

  silent normal! gg
  setlocal buftype=nofile bufhidden=hide nobuflisted
  setlocal nomodifiable noswapfile
  setlocal filetype=help
  setlocal nonumber norelativenumber nowrap
  setlocal foldmethod=diff foldlevel=20

  nnoremap <silent> <buffer> ? :q!<CR>:copen<CR>:call <SID>BindQuickShortcuts()<CR>
endfunction

function! codeclimate#sid()
  return maparg('<SID>', 'n')
endfunction
nnoremap <SID>  <SID>

command! CodeClimateAnalyzeProject :call <SID>AnalyzeProject()
command! CodeClimateAnalyzeOpenFiles :call <SID>AnalyzeOpenFiles()
command! CodeClimateAnalyzeCurrentFile :call <SID>AnalyzeCurrentFile()