spotbugs.vim 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. " Vim compiler file
  2. " Compiler: Spotbugs (Java static checker; needs javac compiled classes)
  3. " Maintainer: @konfekt and @zzzyxwvut
  4. " Last Change: 2024 Nov 27
  5. if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9
  6. finish
  7. endif
  8. let s:cpo_save = &cpo
  9. set cpo&vim
  10. " Unfortunately Spotbugs does not output absolute paths, so you need to
  11. " pass the directory of the files being checked as `-sourcepath` parameter.
  12. " The regex, auxpath and glob try to include all dependent classes of the
  13. " current buffer. See https://github.com/spotbugs/spotbugs/issues/856
  14. " FIXME: When "search()" is used with the "e" flag, it makes no _further_
  15. " progress after claiming an EOL match (i.e. "\_" or "\n", but not "$").
  16. " XXX: Omit anonymous class declarations
  17. let s:keywords = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\|package\)\%(\s\|$\)'
  18. let s:type_names = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\)\s*\(\K\k*\)\>'
  19. " Capture ";" for counting a class file directory (see s:package_dir_heads below)
  20. let s:package_names = '\C\<package\s*\(\K\%(\k*\.\=\)\+;\)'
  21. let s:package = ''
  22. if has('syntax') && exists('g:syntax_on') && exists('b:current_syntax') &&
  23. \ b:current_syntax == 'java' && hlexists('javaClassDecl')
  24. function! s:GetDeclaredTypeNames() abort
  25. if bufname() =~# '\<\%(module\|package\)-info\.java\=$'
  26. return [expand('%:t:r')]
  27. endif
  28. defer execute('silent! normal! g``')
  29. call cursor(1, 1)
  30. let type_names = []
  31. let lnum = search(s:keywords, 'eW')
  32. while lnum > 0
  33. let name_attr = synIDattr(synID(lnum, (col('.') - 1), 0), 'name')
  34. if name_attr ==# 'javaClassDecl'
  35. let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:type_names)
  36. if !empty(tokens) | call add(type_names, tokens[1]) | endif
  37. elseif name_attr ==# 'javaExternal'
  38. let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:package_names)
  39. if !empty(tokens) | let s:package = tokens[1] | endif
  40. endif
  41. let lnum = search(s:keywords, 'eW')
  42. endwhile
  43. return type_names
  44. endfunction
  45. else
  46. function! s:GetDeclaredTypeNames() abort
  47. if bufname() =~# '\<\%(module\|package\)-info\.java\=$'
  48. return [expand('%:t:r')]
  49. endif
  50. " Undo the unsetting of &hls, see below
  51. if &hls
  52. defer execute('set hls')
  53. endif
  54. " Possibly restore the current values for registers '"' and "y", see below
  55. defer call('setreg', ['"', getreg('"'), getregtype('"')])
  56. defer call('setreg', ['y', getreg('y'), getregtype('y')])
  57. defer execute('silent bwipeout')
  58. " Copy buffer contents for modification
  59. silent %y y
  60. new
  61. " Apply ":help scratch-buffer" effects and match "$" in Java (generated)
  62. " type names (see s:type_names)
  63. setlocal iskeyword+=$ buftype=nofile bufhidden=hide noswapfile nohls
  64. 0put y
  65. " Discard text blocks and strings
  66. silent keeppatterns %s/\\\@<!"""\_.\{-}\\\@<!"""\|\\"//ge
  67. silent keeppatterns %s/".*"//ge
  68. " Discard comments
  69. silent keeppatterns %s/\/\/.\+$//ge
  70. silent keeppatterns %s/\/\*\_.\{-}\*\///ge
  71. call cursor(1, 1)
  72. let type_names = []
  73. let lnum = search(s:keywords, 'eW')
  74. while lnum > 0
  75. let line = getline(lnum)
  76. if line =~# '\<package\>'
  77. let tokens = matchlist(line..getline(lnum + 1), s:package_names)
  78. if !empty(tokens) | let s:package = tokens[1] | endif
  79. else
  80. let tokens = matchlist(line..getline(lnum + 1), s:type_names)
  81. if !empty(tokens) | call add(type_names, tokens[1]) | endif
  82. endif
  83. let lnum = search(s:keywords, 'eW')
  84. endwhile
  85. return type_names
  86. endfunction
  87. endif
  88. if has('win32')
  89. function! s:GlobClassFiles(src_type_name) abort
  90. return glob(a:src_type_name..'$*.class', 1, 1)
  91. endfunction
  92. else
  93. function! s:GlobClassFiles(src_type_name) abort
  94. return glob(a:src_type_name..'\$*.class', 1, 1)
  95. endfunction
  96. endif
  97. if exists('g:spotbugs_properties') &&
  98. \ (has_key(g:spotbugs_properties, 'sourceDirPath') &&
  99. \ has_key(g:spotbugs_properties, 'classDirPath')) ||
  100. \ (has_key(g:spotbugs_properties, 'testSourceDirPath') &&
  101. \ has_key(g:spotbugs_properties, 'testClassDirPath'))
  102. function! s:FindClassFiles(src_type_name) abort
  103. let class_files = []
  104. " Match pairwise the components of source and class pathnames
  105. for [src_dir, bin_dir] in filter([
  106. \ [get(g:spotbugs_properties, 'sourceDirPath', ''),
  107. \ get(g:spotbugs_properties, 'classDirPath', '')],
  108. \ [get(g:spotbugs_properties, 'testSourceDirPath', ''),
  109. \ get(g:spotbugs_properties, 'testClassDirPath', '')]],
  110. \ '!(empty(v:val[0]) || empty(v:val[1]))')
  111. " Since only the rightmost "src" is sought, while there can be any number of
  112. " such filenames, no "fnamemodify(a:src_type_name, ':p:s?src?bin?')" is used
  113. let tail_idx = strridx(a:src_type_name, src_dir)
  114. " No such directory or no such inner type (i.e. without "$")
  115. if tail_idx < 0 | continue | endif
  116. " Substitute "bin_dir" for the rightmost "src_dir"
  117. let candidate_type_name = strpart(a:src_type_name, 0, tail_idx)..
  118. \ bin_dir..
  119. \ strpart(a:src_type_name, (tail_idx + strlen(src_dir)))
  120. for candidate in insert(s:GlobClassFiles(candidate_type_name),
  121. \ candidate_type_name..'.class')
  122. if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
  123. endfor
  124. if !empty(class_files) | break | endif
  125. endfor
  126. return class_files
  127. endfunction
  128. else
  129. function! s:FindClassFiles(src_type_name) abort
  130. let class_files = []
  131. for candidate in insert(s:GlobClassFiles(a:src_type_name),
  132. \ a:src_type_name..'.class')
  133. if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
  134. endfor
  135. return class_files
  136. endfunction
  137. endif
  138. function! s:CollectClassFiles() abort
  139. " Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'"
  140. let pathname = expand('%:p')
  141. let tail_idx = strridx(pathname, expand('%:t'))
  142. let src_pathname = strpart(pathname, 0, tail_idx)
  143. let all_class_files = []
  144. " Get all type names in the current buffer and let the filename globbing
  145. " discover inner type names from arbitrary type names
  146. for type_name in s:GetDeclaredTypeNames()
  147. call extend(all_class_files, s:FindClassFiles(src_pathname..type_name))
  148. endfor
  149. return all_class_files
  150. endfunction
  151. " Expose class files for removal etc.
  152. let b:spotbugs_class_files = s:CollectClassFiles()
  153. let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g'))))
  154. let g:current_compiler = 'spotbugs'
  155. " CompilerSet makeprg=spotbugs
  156. let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '..
  157. \ get(b:, 'spotbugs_makeprg_params', get(g:, 'spotbugs_makeprg_params', '-workHard -experimental'))..
  158. \ ' -textui -emacs -auxclasspath %:p'..s:package_dir_heads..':S -sourcepath %:p'..s:package_dir_heads..':S '..
  159. \ join(b:spotbugs_class_files, ' ')
  160. " Emacs expects doubled line numbers
  161. setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m
  162. " " This compiler is meant to be used for a single buffer only
  163. " exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"')
  164. " exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"')
  165. delfunction s:CollectClassFiles
  166. delfunction s:FindClassFiles
  167. delfunction s:GlobClassFiles
  168. delfunction s:GetDeclaredTypeNames
  169. let &cpo = s:cpo_save
  170. unlet s:package_dir_heads s:package s:package_names s:type_names s:keywords s:cpo_save
  171. " vim: set foldmethod=syntax shiftwidth=2 expandtab: