codekingpro/portable-devtools
114k
1" Vim compiler file2" Compiler: Spotbugs (Java static checker; needs javac compiled classes)3" Maintainers: @konfekt and @zzzyxwvut4" Last Change: 2024 Dec 205 6if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 97 finish8endif9 10let s:cpo_save = &cpo11set cpo&vim12 13" Unfortunately Spotbugs does not output absolute paths, so you need to14" pass the directory of the files being checked as `-sourcepath` parameter.15" The regex, auxpath and glob try to include all dependent classes of the16" current buffer. See https://github.com/spotbugs/spotbugs/issues/85617 18" FIXME: When "search()" is used with the "e" flag, it makes no _further_19" progress after claiming an EOL match (i.e. "\_" or "\n", but not "$").20" XXX: Omit anonymous class declarations21let s:keywords = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\|package\)\%(\s\|$\)'22let s:type_names = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\)\s*\(\K\k*\)\>'23" Capture ";" for counting a class file directory (see s:package_dir_heads below)24let s:package_names = '\C\<package\s*\(\K\%(\k*\.\=\)\+;\)'25let s:package = ''26 27if has('syntax') && exists('g:syntax_on') &&28 \ exists('b:current_syntax') && b:current_syntax == 'java' &&29 \ hlexists('javaClassDecl') && hlexists('javaExternal')30 31 function! s:GetDeclaredTypeNames() abort32 if bufname() =~# '\<\%(module\|package\)-info\.java\=$'33 return [expand('%:t:r')]34 endif35 defer execute('silent! normal! g``')36 call cursor(1, 1)37 let type_names = []38 let lnum = search(s:keywords, 'eW')39 while lnum > 040 let name_attr = synIDattr(synID(lnum, (col('.') - 1), 0), 'name')41 if name_attr ==# 'javaClassDecl'42 let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:type_names)43 if !empty(tokens) | call add(type_names, tokens[1]) | endif44 elseif name_attr ==# 'javaExternal'45 let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:package_names)46 if !empty(tokens) | let s:package = tokens[1] | endif47 endif48 let lnum = search(s:keywords, 'eW')49 endwhile50 return type_names51 endfunction52 53else54 function! s:GetDeclaredTypeNames() abort55 if bufname() =~# '\<\%(module\|package\)-info\.java\=$'56 return [expand('%:t:r')]57 endif58 " Undo the unsetting of &hls, see below59 if &hls60 defer execute('set hls')61 endif62 " Possibly restore the current values for registers '"' and "y", see below63 defer call('setreg', ['"', getreg('"'), getregtype('"')])64 defer call('setreg', ['y', getreg('y'), getregtype('y')])65 defer execute('silent bwipeout')66 " Copy buffer contents for modification67 silent %y y68 new69 " Apply ":help scratch-buffer" effects and match "$" in Java (generated)70 " type names (see s:type_names)71 setlocal iskeyword+=$ buftype=nofile bufhidden=hide noswapfile nohls72 0put y73 " Discard text blocks and strings74 silent keeppatterns %s/\\\@<!"""\_.\{-}\\\@<!"""\|\\"//ge75 silent keeppatterns %s/".*"//ge76 " Discard comments77 silent keeppatterns %s/\/\/.\+$//ge78 silent keeppatterns %s/\/\*\_.\{-}\*\///ge79 call cursor(1, 1)80 let type_names = []81 let lnum = search(s:keywords, 'eW')82 while lnum > 083 let line = getline(lnum)84 if line =~# '\<package\>'85 let tokens = matchlist(line..getline(lnum + 1), s:package_names)86 if !empty(tokens) | let s:package = tokens[1] | endif87 else88 let tokens = matchlist(line..getline(lnum + 1), s:type_names)89 if !empty(tokens) | call add(type_names, tokens[1]) | endif90 endif91 let lnum = search(s:keywords, 'eW')92 endwhile93 return type_names94 endfunction95endif96 97if has('win32')98 99 function! s:GlobClassFiles(src_type_name) abort100 return glob(a:src_type_name..'$*.class', 1, 1)101 endfunction102 103else104 function! s:GlobClassFiles(src_type_name) abort105 return glob(a:src_type_name..'\$*.class', 1, 1)106 endfunction107endif108 109if exists('b:spotbugs_properties')110 " Let "ftplugin/java.vim" merge global entries, if any, in buffer-local111 " entries112 113 function! s:GetProperty(name, default) abort114 return get(b:spotbugs_properties, a:name, a:default)115 endfunction116 117elseif exists('g:spotbugs_properties')118 119 function! s:GetProperty(name, default) abort120 return get(g:spotbugs_properties, a:name, a:default)121 endfunction122 123else124 function! s:GetProperty(dummy, default) abort125 return a:default126 endfunction127endif128 129if (exists('g:spotbugs_properties') || exists('b:spotbugs_properties')) &&130 \ ((!empty(s:GetProperty('sourceDirPath', [])) &&131 \ !empty(s:GetProperty('classDirPath', []))) ||132 \ (!empty(s:GetProperty('testSourceDirPath', [])) &&133 \ !empty(s:GetProperty('testClassDirPath', []))))134 135 function! s:CommonIdxsAndDirs() abort136 let src_dir_path = s:GetProperty('sourceDirPath', [])137 let bin_dir_path = s:GetProperty('classDirPath', [])138 let test_src_dir_path = s:GetProperty('testSourceDirPath', [])139 let test_bin_dir_path = s:GetProperty('testClassDirPath', [])140 let dir_cnt = min([len(src_dir_path), len(bin_dir_path)])141 let test_dir_cnt = min([len(test_src_dir_path), len(test_bin_dir_path)])142 " Do not break up path pairs with filtering!143 return [[range(dir_cnt),144 \ src_dir_path[0 : dir_cnt - 1],145 \ bin_dir_path[0 : dir_cnt - 1]],146 \ [range(test_dir_cnt),147 \ test_src_dir_path[0 : test_dir_cnt - 1],148 \ test_bin_dir_path[0 : test_dir_cnt - 1]]]149 endfunction150 151 let s:common_idxs_and_dirs = s:CommonIdxsAndDirs()152 delfunction s:CommonIdxsAndDirs153 154 function! s:FindClassFiles(src_type_name) abort155 let class_files = []156 " Match pairwise the components of source and class pathnames157 for [idxs, src_dirs, bin_dirs] in s:common_idxs_and_dirs158 " Do not use "fnamemodify(a:src_type_name, ':p:s?src?bin?')" because159 " only the rightmost "src" is looked for160 for idx in idxs161 let tail_idx = strridx(a:src_type_name, src_dirs[idx])162 " No such directory or no such inner type (i.e. without "$")163 if tail_idx < 0 | continue | endif164 " Substitute "bin_dirs[idx]" for the rightmost "src_dirs[idx]"165 let candidate_type_name = strpart(a:src_type_name, 0, tail_idx)..166 \ bin_dirs[idx]..167 \ strpart(a:src_type_name, (tail_idx + strlen(src_dirs[idx])))168 for candidate in insert(s:GlobClassFiles(candidate_type_name),169 \ candidate_type_name..'.class')170 if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif171 endfor172 if !empty(class_files) | break | endif173 endfor174 if !empty(class_files) | break | endif175 endfor176 return class_files177 endfunction178 179else180 function! s:FindClassFiles(src_type_name) abort181 let class_files = []182 for candidate in insert(s:GlobClassFiles(a:src_type_name),183 \ a:src_type_name..'.class')184 if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif185 endfor186 return class_files187 endfunction188endif189 190if exists('g:spotbugs_alternative_path') &&191 \ !empty(get(g:spotbugs_alternative_path, 'fromPath', '')) &&192 \ !empty(get(g:spotbugs_alternative_path, 'toPath', ''))193 194 " See https://github.com/spotbugs/spotbugs/issues/909195 function! s:ResolveAbsolutePathname() abort196 let pathname = expand('%:p')197 let head_idx = stridx(pathname, g:spotbugs_alternative_path.toPath)198 " No such file: a mismatched path request for a project199 if head_idx < 0 | return pathname | endif200 " Settle for failure with file readability tests _in s:FindClassFiles()_201 return strpart(pathname, 0, head_idx)..202 \ g:spotbugs_alternative_path.fromPath..203 \ strpart(pathname, (head_idx + strlen(g:spotbugs_alternative_path.toPath)))204 endfunction205 206else207 function! s:ResolveAbsolutePathname() abort208 return expand('%:p')209 endfunction210endif211 212function! s:CollectClassFiles() abort213 " Possibly obtain a symlinked path for an unsupported directory name214 let pathname = s:ResolveAbsolutePathname()215 " Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'"216 let tail_idx = strridx(pathname, expand('%:t'))217 let src_pathname = strpart(pathname, 0, tail_idx)218 let all_class_files = []219 " Get all type names in the current buffer and let the filename globbing220 " discover inner type names from arbitrary type names221 for type_name in s:GetDeclaredTypeNames()222 call extend(all_class_files, s:FindClassFiles(src_pathname..type_name))223 endfor224 return all_class_files225endfunction226 227" Expose class files for removal etc.228let b:spotbugs_class_files = s:CollectClassFiles()229let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g'))))230let s:package_root_dir = fnamemodify(s:ResolveAbsolutePathname(), s:package_dir_heads..':S')231let g:current_compiler = 'spotbugs'232" CompilerSet makeprg=spotbugs233let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '..234 \ get(b:, 'spotbugs_makeprg_params', get(g:, 'spotbugs_makeprg_params', '-workHard -experimental'))..235 \ ' -textui -emacs -auxclasspath '..s:package_root_dir..' -sourcepath '..s:package_root_dir..' '..236 \ join(b:spotbugs_class_files, ' ')237" Emacs expects doubled line numbers238setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m239 240" " This compiler is meant to be used for a single buffer only241" exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"')242" exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"')243 244delfunction s:CollectClassFiles245delfunction s:ResolveAbsolutePathname246delfunction s:FindClassFiles247delfunction s:GetProperty248delfunction s:GlobClassFiles249delfunction s:GetDeclaredTypeNames250let &cpo = s:cpo_save251unlet! s:package_root_dir s:package_dir_heads s:common_idxs_and_dirs s:package252unlet! s:package_names s:type_names s:keywords s:cpo_save253 254" vim: set foldmethod=syntax shiftwidth=2 expandtab:255 