This commit is contained in:
zeertzjq 2024-12-19 14:17:32 +08:00 committed by GitHub
commit 75f918c5ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 880 additions and 175 deletions

View File

@ -1,11 +1,52 @@
" Default pre- and post-compiler actions for SpotBugs " Default pre- and post-compiler actions and commands for SpotBugs
" Maintainers: @konfekt and @zzzyxwvut " Maintainers: @konfekt and @zzzyxwvut
" Last Change: 2024 Nov 27 " Last Change: 2024 Dec 08
let s:save_cpo = &cpo let s:save_cpo = &cpo
set cpo&vim set cpo&vim
if v:version > 900 " Look for the setting of "g:spotbugs#state" in "ftplugin/java.vim".
let s:state = get(g:, 'spotbugs#state', {})
let s:commands = get(s:state, 'commands', {})
let s:compiler = get(s:state, 'compiler', '')
let s:readable = filereadable($VIMRUNTIME . '/compiler/' . s:compiler . '.vim')
if has_key(s:commands, 'DefaultPreCompilerCommand')
let g:SpotBugsPreCompilerCommand = s:commands.DefaultPreCompilerCommand
else
function! s:DefaultPreCompilerCommand(arguments) abort
execute 'make ' . a:arguments
cc
endfunction
let g:SpotBugsPreCompilerCommand = function('s:DefaultPreCompilerCommand')
endif
if has_key(s:commands, 'DefaultPreCompilerTestCommand')
let g:SpotBugsPreCompilerTestCommand = s:commands.DefaultPreCompilerTestCommand
else
function! s:DefaultPreCompilerTestCommand(arguments) abort
execute 'make ' . a:arguments
cc
endfunction
let g:SpotBugsPreCompilerTestCommand = function('s:DefaultPreCompilerTestCommand')
endif
if has_key(s:commands, 'DefaultPostCompilerCommand')
let g:SpotBugsPostCompilerCommand = s:commands.DefaultPostCompilerCommand
else
function! s:DefaultPostCompilerCommand(arguments) abort
execute 'make ' . a:arguments
endfunction
let g:SpotBugsPostCompilerCommand = function('s:DefaultPostCompilerCommand')
endif
if v:version > 900 || has('nvim')
function! spotbugs#DeleteClassFiles() abort function! spotbugs#DeleteClassFiles() abort
if !exists('b:spotbugs_class_files') if !exists('b:spotbugs_class_files')
@ -24,7 +65,9 @@ if v:version > 900
" Test the magic number and the major version number (45 for v1.0). " Test the magic number and the major version number (45 for v1.0).
" Since v9.0.2027. " Since v9.0.2027.
if len(octad) == 8 && octad[0 : 3] == 0zcafe.babe && if len(octad) == 8 && octad[0 : 3] == 0zcafe.babe &&
\ or((octad[6] << 8), octad[7]) >= 45 " Nvim: no << operator
"\ or((octad[6] << 8), octad[7]) >= 45
\ or((octad[6] * 256), octad[7]) >= 45
echomsg printf('Deleting %s: %d', classname, delete(classname)) echomsg printf('Deleting %s: %d', classname, delete(classname))
endif endif
endif endif
@ -127,25 +170,21 @@ endif
function! spotbugs#DefaultPostCompilerAction() abort function! spotbugs#DefaultPostCompilerAction() abort
" Since v7.4.191. " Since v7.4.191.
make %:S call call(g:SpotBugsPostCompilerCommand, ['%:S'])
endfunction endfunction
" Look for "spotbugs#compiler" in "ftplugin/java.vim".
let s:compiler = exists('spotbugs#compiler') ? spotbugs#compiler : ''
let s:readable = filereadable($VIMRUNTIME . '/compiler/' . s:compiler . '.vim')
if s:readable && s:compiler ==# 'maven' && executable('mvn') if s:readable && s:compiler ==# 'maven' && executable('mvn')
function! spotbugs#DefaultPreCompilerAction() abort function! spotbugs#DefaultPreCompilerAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler maven compiler maven
make compile call call(g:SpotBugsPreCompilerCommand, ['compile'])
endfunction endfunction
function! spotbugs#DefaultPreCompilerTestAction() abort function! spotbugs#DefaultPreCompilerTestAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler maven compiler maven
make test-compile call call(g:SpotBugsPreCompilerTestCommand, ['test-compile'])
endfunction endfunction
function! spotbugs#DefaultProperties() abort function! spotbugs#DefaultProperties() abort
@ -156,10 +195,10 @@ if s:readable && s:compiler ==# 'maven' && executable('mvn')
\ function('spotbugs#DefaultPreCompilerTestAction'), \ function('spotbugs#DefaultPreCompilerTestAction'),
\ 'PostCompilerAction': \ 'PostCompilerAction':
\ function('spotbugs#DefaultPostCompilerAction'), \ function('spotbugs#DefaultPostCompilerAction'),
\ 'sourceDirPath': 'src/main/java', \ 'sourceDirPath': ['src/main/java'],
\ 'classDirPath': 'target/classes', \ 'classDirPath': ['target/classes'],
\ 'testSourceDirPath': 'src/test/java', \ 'testSourceDirPath': ['src/test/java'],
\ 'testClassDirPath': 'target/test-classes', \ 'testClassDirPath': ['target/test-classes'],
\ } \ }
endfunction endfunction
@ -169,13 +208,13 @@ elseif s:readable && s:compiler ==# 'ant' && executable('ant')
function! spotbugs#DefaultPreCompilerAction() abort function! spotbugs#DefaultPreCompilerAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler ant compiler ant
make compile call call(g:SpotBugsPreCompilerCommand, ['compile'])
endfunction endfunction
function! spotbugs#DefaultPreCompilerTestAction() abort function! spotbugs#DefaultPreCompilerTestAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler ant compiler ant
make compile-test call call(g:SpotBugsPreCompilerTestCommand, ['compile-test'])
endfunction endfunction
function! spotbugs#DefaultProperties() abort function! spotbugs#DefaultProperties() abort
@ -186,28 +225,55 @@ elseif s:readable && s:compiler ==# 'ant' && executable('ant')
\ function('spotbugs#DefaultPreCompilerTestAction'), \ function('spotbugs#DefaultPreCompilerTestAction'),
\ 'PostCompilerAction': \ 'PostCompilerAction':
\ function('spotbugs#DefaultPostCompilerAction'), \ function('spotbugs#DefaultPostCompilerAction'),
\ 'sourceDirPath': 'src', \ 'sourceDirPath': ['src'],
\ 'classDirPath': 'build/classes', \ 'classDirPath': ['build/classes'],
\ 'testSourceDirPath': 'test', \ 'testSourceDirPath': ['test'],
\ 'testClassDirPath': 'build/test/classes', \ 'testClassDirPath': ['build/test/classes'],
\ } \ }
endfunction endfunction
unlet s:readable s:compiler unlet s:readable s:compiler
elseif s:readable && s:compiler ==# 'javac' && executable('javac') elseif s:readable && s:compiler ==# 'javac' && executable('javac')
let s:filename = tempname()
function! spotbugs#DefaultPreCompilerAction() abort function! spotbugs#DefaultPreCompilerAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler javac compiler javac
if get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')) =~ '\s@\S' if get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')) =~ '\s@\S'
" Read options and filenames from @options [@sources ...]. " Only read options and filenames from @options [@sources ...] and do
make " not update these files when filelists change.
call call(g:SpotBugsPreCompilerCommand, [''])
else else
" Let Javac figure out what files to compile. " Collect filenames so that Javac can figure out what to compile.
execute 'make ' . join(map(filter(copy(v:argv), let filelist = []
\ "v:val =~# '\\.java\\=$'"),
\ 'shellescape(v:val)'), ' ') for arg_num in range(argc(-1))
let arg_name = argv(arg_num)
if arg_name =~# '\.java\=$'
call add(filelist, fnamemodify(arg_name, ':p:S'))
endif
endfor
for buf_num in range(1, bufnr('$'))
if !buflisted(buf_num)
continue
endif
let buf_name = bufname(buf_num)
if buf_name =~# '\.java\=$'
let buf_name = fnamemodify(buf_name, ':p:S')
if index(filelist, buf_name) < 0
call add(filelist, buf_name)
endif
endif
endfor
noautocmd call writefile(filelist, s:filename)
call call(g:SpotBugsPreCompilerCommand, [shellescape('@' . s:filename)])
endif endif
endfunction endfunction
@ -219,14 +285,13 @@ elseif s:readable && s:compiler ==# 'javac' && executable('javac')
return { return {
\ 'PreCompilerAction': \ 'PreCompilerAction':
\ function('spotbugs#DefaultPreCompilerAction'), \ function('spotbugs#DefaultPreCompilerAction'),
\ 'PreCompilerTestAction':
\ function('spotbugs#DefaultPreCompilerTestAction'),
\ 'PostCompilerAction': \ 'PostCompilerAction':
\ function('spotbugs#DefaultPostCompilerAction'), \ function('spotbugs#DefaultPostCompilerAction'),
\ } \ }
endfunction endfunction
unlet s:readable s:compiler unlet s:readable s:compiler g:SpotBugsPreCompilerTestCommand
delfunction! s:DefaultPreCompilerTestCommand
else else
function! spotbugs#DefaultPreCompilerAction() abort function! spotbugs#DefaultPreCompilerAction() abort
@ -241,10 +306,49 @@ else
return {} return {}
endfunction endfunction
unlet s:readable " XXX: Keep "s:compiler" around for "spotbugs#DefaultPreCompilerAction()",
" "s:DefaultPostCompilerCommand" -- "spotbugs#DefaultPostCompilerAction()".
unlet s:readable g:SpotBugsPreCompilerCommand g:SpotBugsPreCompilerTestCommand
delfunction! s:DefaultPreCompilerCommand
delfunction! s:DefaultPreCompilerTestCommand
endif endif
function! s:DefineBufferAutocmd(event, ...) abort
if !exists('#java_spotbugs#User')
return 1
endif
for l:event in insert(copy(a:000), a:event)
if l:event != 'User'
execute printf('silent! autocmd! java_spotbugs %s <buffer>', l:event)
execute printf('autocmd java_spotbugs %s <buffer> doautocmd User', l:event)
endif
endfor
return 0
endfunction
function! s:RemoveBufferAutocmd(event, ...) abort
if !exists('#java_spotbugs')
return 1
endif
for l:event in insert(copy(a:000), a:event)
if l:event != 'User'
execute printf('silent! autocmd! java_spotbugs %s <buffer>', l:event)
endif
endfor
return 0
endfunction
" Documented in ":help compiler-spotbugs".
command! -bar -nargs=+ -complete=event SpotBugsDefineBufferAutocmd
\ call s:DefineBufferAutocmd(<f-args>)
command! -bar -nargs=+ -complete=event SpotBugsRemoveBufferAutocmd
\ call s:RemoveBufferAutocmd(<f-args>)
let &cpo = s:save_cpo let &cpo = s:save_cpo
unlet s:save_cpo unlet s:commands s:state s:save_cpo
" vim: set foldmethod=syntax shiftwidth=2 expandtab: " vim: set foldmethod=syntax shiftwidth=2 expandtab:

View File

@ -1,7 +1,7 @@
" Vim compiler file " Vim compiler file
" Compiler: Spotbugs (Java static checker; needs javac compiled classes) " Compiler: Spotbugs (Java static checker; needs javac compiled classes)
" Maintainer: @konfekt and @zzzyxwvut " Maintainer: @konfekt and @zzzyxwvut
" Last Change: 2024 Nov 27 " Last Change: 2024 Dec 14
if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9 if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9
finish finish
@ -24,8 +24,9 @@ let s:type_names = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\)\s*\(\K\k*\
let s:package_names = '\C\<package\s*\(\K\%(\k*\.\=\)\+;\)' let s:package_names = '\C\<package\s*\(\K\%(\k*\.\=\)\+;\)'
let s:package = '' let s:package = ''
if has('syntax') && exists('g:syntax_on') && exists('b:current_syntax') && if has('syntax') && exists('g:syntax_on') &&
\ b:current_syntax == 'java' && hlexists('javaClassDecl') \ exists('b:current_syntax') && b:current_syntax == 'java' &&
\ hlexists('javaClassDecl') && hlexists('javaExternal')
function! s:GetDeclaredTypeNames() abort function! s:GetDeclaredTypeNames() abort
if bufname() =~# '\<\%(module\|package\)-info\.java\=$' if bufname() =~# '\<\%(module\|package\)-info\.java\=$'
@ -105,53 +106,113 @@ else
endfunction endfunction
endif endif
if exists('g:spotbugs_properties') && if exists('b:spotbugs_properties')
\ (has_key(g:spotbugs_properties, 'sourceDirPath') && " Let "ftplugin/java.vim" merge global entries, if any, in buffer-local
\ has_key(g:spotbugs_properties, 'classDirPath')) || " entries
\ (has_key(g:spotbugs_properties, 'testSourceDirPath') &&
\ has_key(g:spotbugs_properties, 'testClassDirPath'))
function! s:FindClassFiles(src_type_name) abort function! s:GetProperty(name, default) abort
let class_files = [] return get(b:spotbugs_properties, a:name, a:default)
" Match pairwise the components of source and class pathnames endfunction
for [src_dir, bin_dir] in filter([
\ [get(g:spotbugs_properties, 'sourceDirPath', ''), elseif exists('g:spotbugs_properties')
\ get(g:spotbugs_properties, 'classDirPath', '')],
\ [get(g:spotbugs_properties, 'testSourceDirPath', ''), function! s:GetProperty(name, default) abort
\ get(g:spotbugs_properties, 'testClassDirPath', '')]], return get(g:spotbugs_properties, a:name, a:default)
\ '!(empty(v:val[0]) || empty(v:val[1]))') endfunction
" Since only the rightmost "src" is sought, while there can be any number of
" such filenames, no "fnamemodify(a:src_type_name, ':p:s?src?bin?')" is used
let tail_idx = strridx(a:src_type_name, src_dir)
" No such directory or no such inner type (i.e. without "$")
if tail_idx < 0 | continue | endif
" Substitute "bin_dir" for the rightmost "src_dir"
let candidate_type_name = strpart(a:src_type_name, 0, tail_idx)..
\ bin_dir..
\ strpart(a:src_type_name, (tail_idx + strlen(src_dir)))
for candidate in insert(s:GlobClassFiles(candidate_type_name),
\ candidate_type_name..'.class')
if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
endfor
if !empty(class_files) | break | endif
endfor
return class_files
endfunction
else else
function! s:FindClassFiles(src_type_name) abort function! s:GetProperty(dummy, default) abort
let class_files = [] return a:default
for candidate in insert(s:GlobClassFiles(a:src_type_name), endfunction
endif
if (exists('g:spotbugs_properties') || exists('b:spotbugs_properties')) &&
\ ((!empty(s:GetProperty('sourceDirPath', [])) &&
\ !empty(s:GetProperty('classDirPath', []))) ||
\ (!empty(s:GetProperty('testSourceDirPath', [])) &&
\ !empty(s:GetProperty('testClassDirPath', []))))
function! s:CommonIdxsAndDirs() abort
let src_dir_path = s:GetProperty('sourceDirPath', [])
let bin_dir_path = s:GetProperty('classDirPath', [])
let test_src_dir_path = s:GetProperty('testSourceDirPath', [])
let test_bin_dir_path = s:GetProperty('testClassDirPath', [])
let dir_cnt = min([len(src_dir_path), len(bin_dir_path)])
let test_dir_cnt = min([len(test_src_dir_path), len(test_bin_dir_path)])
" Do not break up path pairs with filtering!
return [[range(dir_cnt),
\ src_dir_path[0 : dir_cnt - 1],
\ bin_dir_path[0 : dir_cnt - 1]],
\ [range(test_dir_cnt),
\ test_src_dir_path[0 : test_dir_cnt - 1],
\ test_bin_dir_path[0 : test_dir_cnt - 1]]]
endfunction
let s:common_idxs_and_dirs = s:CommonIdxsAndDirs()
delfunction s:CommonIdxsAndDirs
function! s:FindClassFiles(src_type_name) abort
let class_files = []
" Match pairwise the components of source and class pathnames
for [idxs, src_dirs, bin_dirs] in s:common_idxs_and_dirs
" Do not use "fnamemodify(a:src_type_name, ':p:s?src?bin?')" because
" only the rightmost "src" is looked for
for idx in idxs
let tail_idx = strridx(a:src_type_name, src_dirs[idx])
" No such directory or no such inner type (i.e. without "$")
if tail_idx < 0 | continue | endif
" Substitute "bin_dirs[idx]" for the rightmost "src_dirs[idx]"
let candidate_type_name = strpart(a:src_type_name, 0, tail_idx)..
\ bin_dirs[idx]..
\ strpart(a:src_type_name, (tail_idx + strlen(src_dirs[idx])))
for candidate in insert(s:GlobClassFiles(candidate_type_name),
\ candidate_type_name..'.class')
if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
endfor
if !empty(class_files) | break | endif
endfor
if !empty(class_files) | break | endif
endfor
return class_files
endfunction
else
function! s:FindClassFiles(src_type_name) abort
let class_files = []
for candidate in insert(s:GlobClassFiles(a:src_type_name),
\ a:src_type_name..'.class') \ a:src_type_name..'.class')
if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
endfor endfor
return class_files return class_files
endfunction endfunction
endif
if exists('g:spotbugs_alternative_path') &&
\ !empty(get(g:spotbugs_alternative_path, 'fromPath', '')) &&
\ !empty(get(g:spotbugs_alternative_path, 'toPath', ''))
" See https://github.com/spotbugs/spotbugs/issues/909
function! s:ResolveAbsolutePathname() abort
let pathname = expand('%:p')
let head_idx = stridx(pathname, g:spotbugs_alternative_path.toPath)
" No such file: a mismatched path request for a project
if head_idx < 0 | return pathname | endif
" Settle for failure with file readability tests _in s:FindClassFiles()_
return strpart(pathname, 0, head_idx)..
\ g:spotbugs_alternative_path.fromPath..
\ strpart(pathname, (head_idx + strlen(g:spotbugs_alternative_path.toPath)))
endfunction
else
function! s:ResolveAbsolutePathname() abort
return expand('%:p')
endfunction
endif endif
function! s:CollectClassFiles() abort function! s:CollectClassFiles() abort
" Possibly obtain a symlinked path for an unsupported directory name
let pathname = s:ResolveAbsolutePathname()
" Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'" " Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'"
let pathname = expand('%:p')
let tail_idx = strridx(pathname, expand('%:t')) let tail_idx = strridx(pathname, expand('%:t'))
let src_pathname = strpart(pathname, 0, tail_idx) let src_pathname = strpart(pathname, 0, tail_idx)
let all_class_files = [] let all_class_files = []
@ -166,11 +227,12 @@ endfunction
" Expose class files for removal etc. " Expose class files for removal etc.
let b:spotbugs_class_files = s:CollectClassFiles() let b:spotbugs_class_files = s:CollectClassFiles()
let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g')))) let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g'))))
let s:package_root_dir = fnamemodify(s:ResolveAbsolutePathname(), s:package_dir_heads..':S')
let g:current_compiler = 'spotbugs' let g:current_compiler = 'spotbugs'
" CompilerSet makeprg=spotbugs " CompilerSet makeprg=spotbugs
let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '.. let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '..
\ get(b:, 'spotbugs_makeprg_params', get(g:, 'spotbugs_makeprg_params', '-workHard -experimental')).. \ get(b:, 'spotbugs_makeprg_params', get(g:, 'spotbugs_makeprg_params', '-workHard -experimental'))..
\ ' -textui -emacs -auxclasspath %:p'..s:package_dir_heads..':S -sourcepath %:p'..s:package_dir_heads..':S '.. \ ' -textui -emacs -auxclasspath '..s:package_root_dir..' -sourcepath '..s:package_root_dir..' '..
\ join(b:spotbugs_class_files, ' ') \ join(b:spotbugs_class_files, ' ')
" Emacs expects doubled line numbers " Emacs expects doubled line numbers
setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m
@ -180,10 +242,13 @@ setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m
" exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"') " exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"')
delfunction s:CollectClassFiles delfunction s:CollectClassFiles
delfunction s:ResolveAbsolutePathname
delfunction s:FindClassFiles delfunction s:FindClassFiles
delfunction s:GetProperty
delfunction s:GlobClassFiles delfunction s:GlobClassFiles
delfunction s:GetDeclaredTypeNames delfunction s:GetDeclaredTypeNames
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:package_dir_heads s:package s:package_names s:type_names s:keywords s:cpo_save unlet! s:package_root_dir s:package_dir_heads s:common_idxs_and_dirs s:package
unlet! s:package_names s:type_names s:keywords s:cpo_save
" vim: set foldmethod=syntax shiftwidth=2 expandtab: " vim: set foldmethod=syntax shiftwidth=2 expandtab:

View File

@ -1335,7 +1335,7 @@ It scans the Java bytecode of all classes in the currently open buffer.
(Therefore, `:compiler! spotbugs` is not supported.) (Therefore, `:compiler! spotbugs` is not supported.)
Commonly used compiler options can be added to 'makeprg' by setting the Commonly used compiler options can be added to 'makeprg' by setting the
"b:" or "g:spotbugs_makeprg_params" variable. For example: > "b:" or "g:spotbugs_makeprg_params" variable. For example: >vim
let b:spotbugs_makeprg_params = "-longBugCodes -effort:max -low" let b:spotbugs_makeprg_params = "-longBugCodes -effort:max -low"
@ -1345,22 +1345,25 @@ By default, the class files are searched in the directory where the source
files are placed. However, typical Java projects use distinct directories files are placed. However, typical Java projects use distinct directories
for source files and class files. To make both known to SpotBugs, assign for source files and class files. To make both known to SpotBugs, assign
their paths (distinct and relative to their common root directory) to the their paths (distinct and relative to their common root directory) to the
following properties (using the example of a common Maven project): > following properties (using the example of a common Maven project): >vim
let g:spotbugs_properties = { let g:spotbugs_properties = {
\ 'sourceDirPath': 'src/main/java', \ 'sourceDirPath': ['src/main/java'],
\ 'classDirPath': 'target/classes', \ 'classDirPath': ['target/classes'],
\ 'testSourceDirPath': 'src/test/java', \ 'testSourceDirPath': ['src/test/java'],
\ 'testClassDirPath': 'target/test-classes', \ 'testClassDirPath': ['target/test-classes'],
\ } \ }
Note that source and class path entries are expected to come in pairs: define
both "sourceDirPath" and "classDirPath" when you are considering at least one,
and apply the same logic to "testSourceDirPath" and "testClassDirPath".
Note that values for the path keys describe only for SpotBugs where to look Note that values for the path keys describe only for SpotBugs where to look
for files; refer to the documentation for particular compiler plugins for more for files; refer to the documentation for particular compiler plugins for more
information. information.
The default pre- and post-compiler actions are provided for Ant, Maven, and The default pre- and post-compiler actions are provided for Ant, Maven, and
Javac compiler plugins and can be selected by assigning the name of a compiler Javac compiler plugins and can be selected by assigning the name of a compiler
plugin to the "compiler" key: > plugin (`ant`, `maven`, or `javac`) to the "compiler" key: >vim
let g:spotbugs_properties = { let g:spotbugs_properties = {
\ 'compiler': 'maven', \ 'compiler': 'maven',
@ -1370,7 +1373,7 @@ This single setting is essentially equivalent to all the settings below, with
the exception made for the "PreCompilerAction" and "PreCompilerTestAction" the exception made for the "PreCompilerAction" and "PreCompilerTestAction"
values: their listed |Funcref|s will obtain no-op implementations whereas the values: their listed |Funcref|s will obtain no-op implementations whereas the
implicit Funcrefs of the "compiler" key will obtain the requested defaults if implicit Funcrefs of the "compiler" key will obtain the requested defaults if
available. > available. >vim
let g:spotbugs_properties = { let g:spotbugs_properties = {
\ 'PreCompilerAction': \ 'PreCompilerAction':
@ -1379,10 +1382,10 @@ available. >
\ function('spotbugs#DefaultPreCompilerTestAction'), \ function('spotbugs#DefaultPreCompilerTestAction'),
\ 'PostCompilerAction': \ 'PostCompilerAction':
\ function('spotbugs#DefaultPostCompilerAction'), \ function('spotbugs#DefaultPostCompilerAction'),
\ 'sourceDirPath': 'src/main/java', \ 'sourceDirPath': ['src/main/java'],
\ 'classDirPath': 'target/classes', \ 'classDirPath': ['target/classes'],
\ 'testSourceDirPath': 'src/test/java', \ 'testSourceDirPath': ['src/test/java'],
\ 'testClassDirPath': 'target/test-classes', \ 'testClassDirPath': ['target/test-classes'],
\ } \ }
With default actions, the compiler of choice will attempt to rebuild the class With default actions, the compiler of choice will attempt to rebuild the class
@ -1390,23 +1393,42 @@ files for the buffer (and possibly for the whole project) as soon as a Java
syntax file is loaded; then, `spotbugs` will attempt to analyze the quality of syntax file is loaded; then, `spotbugs` will attempt to analyze the quality of
the compilation unit of the buffer. the compilation unit of the buffer.
When default actions are not suited to a desired workflow, consider writing Vim commands proficient in 'makeprg' [0] can be composed with default actions.
arbitrary functions yourself and matching their |Funcref|s to the supported Begin by considering which of the supported keys, "DefaultPreCompilerCommand",
"DefaultPreCompilerTestCommand", or "DefaultPostCompilerCommand", you need to
write an implementation for, observing that each of these keys corresponds to
a particular "*Action" key. Follow it by defining a new function that always
declares an only parameter of type string and puts to use a command equivalent
of |:make|, and assigning its |Funcref| to the selected key. For example:
>vim
function! GenericPostCompilerCommand(arguments) abort
execute 'make ' . a:arguments
endfunction
let g:spotbugs_properties = {
\ 'DefaultPostCompilerCommand':
\ function('GenericPostCompilerCommand'),
\ }
When default actions are not suited to a desired workflow, proceed by writing
arbitrary functions yourself and matching their Funcrefs to the supported
keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction". keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction".
The next example re-implements the default pre-compiler actions for a Maven The next example re-implements the default pre-compiler actions for a Maven
project and requests other default Maven settings with the "compiler" entry: > project and requests other default Maven settings with the "compiler" entry:
>vim
function! MavenPreCompilerAction() abort function! MavenPreCompilerAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler maven compiler maven
make compile make compile
cc
endfunction endfunction
function! MavenPreCompilerTestAction() abort function! MavenPreCompilerTestAction() abort
call spotbugs#DeleteClassFiles() call spotbugs#DeleteClassFiles()
compiler maven compiler maven
make test-compile make test-compile
cc
endfunction endfunction
let g:spotbugs_properties = { let g:spotbugs_properties = {
@ -1419,14 +1441,46 @@ project and requests other default Maven settings with the "compiler" entry: >
Note that all entered custom settings will take precedence over the matching Note that all entered custom settings will take precedence over the matching
default settings in "g:spotbugs_properties". default settings in "g:spotbugs_properties".
Note that it is necessary to notify the plugin of the result of a pre-compiler
action before further work can be undertaken. Using |:cc| after |:make| (or
|:ll| after |:lmake|) as the last command of an action is the supported means
of such communication.
Two commands, "SpotBugsRemoveBufferAutocmd" and "SpotBugsDefineBufferAutocmd",
are provided to toggle actions for buffer-local autocommands. For example, to
also run actions on any |BufWritePost| and |Signal| event, add these lines to
`~/.config/nvim/after/ftplugin/java.vim`: >vim
if exists(':SpotBugsDefineBufferAutocmd') == 2
SpotBugsDefineBufferAutocmd BufWritePost Signal
endif
Otherwise, you can turn to `:doautocmd java_spotbugs User` at any time.
The "g:spotbugs_properties" variable is consulted by the Java filetype plugin The "g:spotbugs_properties" variable is consulted by the Java filetype plugin
(|ft-java-plugin|) to arrange for the described automation, and, therefore, it (|ft-java-plugin|) to arrange for the described automation, and, therefore, it
must be defined before |FileType| events can take place for the buffers loaded must be defined before |FileType| events can take place for the buffers loaded
with Java source files. It could, for example, be set in a project-local with Java source files. It could, for example, be set in a project-local
|vimrc| loaded by [0]. |vimrc| loaded by [1].
[0] https://github.com/MarcWeber/vim-addon-local-vimrc/ Both "g:spotbugs_properties" and "b:spotbugs_properties" are recognized and
must be modifiable (|:unlockvar|). The "*Command" entries are always treated
as global functions to be shared among all Java buffers.
The SpotBugs Java library and, by extension, its distributed shell scripts do
not support in the `-textui` mode listed pathnames with directory filenames
that contain blank characters [2]. To work around this limitation, consider
making a symbolic link to such a directory from a directory that does not have
blank characters in its name and passing this information to SpotBugs: >vim
let g:spotbugs_alternative_path = {
\ 'fromPath': 'path/to/dir_without_blanks',
\ 'toPath': 'path/to/dir with blanks',
\ }
[0] https://github.com/Konfekt/vim-compilers
[1] https://github.com/MarcWeber/vim-addon-local-vimrc
[2] https://github.com/spotbugs/spotbugs/issues/909
GNU MAKE *compiler-make* GNU MAKE *compiler-make*

View File

@ -3,7 +3,7 @@
" Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com>
" Former Maintainer: Dan Sharp " Former Maintainer: Dan Sharp
" Repository: https://github.com/zzzyxwvut/java-vim.git " Repository: https://github.com/zzzyxwvut/java-vim.git
" Last Change: 2024 Nov 24 " Last Change: 2024 Dec 16
" 2024 Jan 14 by Vim Project (browsefilter) " 2024 Jan 14 by Vim Project (browsefilter)
" 2024 May 23 by Riley Bruins <ribru17@gmail.com> ('commentstring') " 2024 May 23 by Riley Bruins <ribru17@gmail.com> ('commentstring')
@ -90,63 +90,155 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
endif endif
endif endif
" The support for pre- and post-compiler actions for SpotBugs. """" Support pre- and post-compiler actions for SpotBugs.
if exists("g:spotbugs_properties") && has_key(g:spotbugs_properties, 'compiler') if (!empty(get(g:, 'spotbugs_properties', {})) ||
try \ !empty(get(b:, 'spotbugs_properties', {}))) &&
let spotbugs#compiler = g:spotbugs_properties.compiler \ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim')
let g:spotbugs_properties = extend(
\ spotbugs#DefaultProperties(),
\ g:spotbugs_properties,
\ 'force')
catch
echomsg v:errmsg
finally
call remove(g:spotbugs_properties, 'compiler')
endtry
endif
if exists("g:spotbugs_properties") && function! s:SpotBugsGetProperty(name, default) abort
\ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim') return get(
\ {s:spotbugs_properties_scope}spotbugs_properties,
\ a:name,
\ a:default)
endfunction
function! s:SpotBugsHasProperty(name) abort
return has_key(
\ {s:spotbugs_properties_scope}spotbugs_properties,
\ a:name)
endfunction
function! s:SpotBugsGetProperties() abort
return {s:spotbugs_properties_scope}spotbugs_properties
endfunction
" Work around ":bar"s and ":autocmd"s.
function! s:ExecuteActionOnce(cleanup_cmd, action_cmd) abort
try
execute a:cleanup_cmd
finally
execute a:action_cmd
endtry
endfunction
if exists("b:spotbugs_properties")
let s:spotbugs_properties_scope = 'b:'
" Merge global entries, if any, in buffer-local entries, favouring
" defined buffer-local ones.
call extend(
\ b:spotbugs_properties,
\ get(g:, 'spotbugs_properties', {}),
\ 'keep')
elseif exists("g:spotbugs_properties")
let s:spotbugs_properties_scope = 'g:'
endif
let s:commands = {}
for s:name in ['DefaultPreCompilerCommand',
\ 'DefaultPreCompilerTestCommand',
\ 'DefaultPostCompilerCommand']
if s:SpotBugsHasProperty(s:name)
let s:commands[s:name] = remove(
\ s:SpotBugsGetProperties(),
\ s:name)
endif
endfor
if s:SpotBugsHasProperty('compiler')
" XXX: Postpone loading the script until all state, if any, has been
" collected.
if !empty(s:commands)
let g:spotbugs#state = {
\ 'compiler': remove(s:SpotBugsGetProperties(), 'compiler'),
\ 'commands': copy(s:commands),
\ }
else
let g:spotbugs#state = {
\ 'compiler': remove(s:SpotBugsGetProperties(), 'compiler'),
\ }
endif
" Merge default entries in global (or buffer-local) entries, favouring
" defined global (or buffer-local) ones.
call extend(
\ {s:spotbugs_properties_scope}spotbugs_properties,
\ spotbugs#DefaultProperties(),
\ 'keep')
elseif !empty(s:commands)
" XXX: Postpone loading the script until all state, if any, has been
" collected.
let g:spotbugs#state = {'commands': copy(s:commands)}
endif
unlet s:commands s:name
let s:request = 0 let s:request = 0
if has_key(g:spotbugs_properties, 'PreCompilerAction') if s:SpotBugsHasProperty('PostCompilerAction')
let s:dispatcher = 'call g:spotbugs_properties.PreCompilerAction() | '
let s:request += 1
endif
if has_key(g:spotbugs_properties, 'PreCompilerTestAction')
let s:dispatcher = 'call g:spotbugs_properties.PreCompilerTestAction() | '
let s:request += 2
endif
if has_key(g:spotbugs_properties, 'PostCompilerAction')
let s:request += 4 let s:request += 4
endif endif
if s:SpotBugsHasProperty('PreCompilerTestAction')
let s:dispatcher = printf('call call(%s, [])',
\ string(s:SpotBugsGetProperties().PreCompilerTestAction))
let s:request += 2
endif
if s:SpotBugsHasProperty('PreCompilerAction')
let s:dispatcher = printf('call call(%s, [])',
\ string(s:SpotBugsGetProperties().PreCompilerAction))
let s:request += 1
endif
" Adapt the tests for "s:FindClassFiles()" from "compiler/spotbugs.vim".
if (s:request == 3 || s:request == 7) && if (s:request == 3 || s:request == 7) &&
\ has_key(g:spotbugs_properties, 'sourceDirPath') && \ (!empty(s:SpotBugsGetProperty('sourceDirPath', [])) &&
\ has_key(g:spotbugs_properties, 'testSourceDirPath') \ !empty(s:SpotBugsGetProperty('classDirPath', [])) &&
function! s:DispatchAction(path_action_pairs) abort \ !empty(s:SpotBugsGetProperty('testSourceDirPath', [])) &&
\ !empty(s:SpotBugsGetProperty('testClassDirPath', [])))
function! s:DispatchAction(paths_action_pairs) abort
let name = expand('%:p') let name = expand('%:p')
for [path, Action] in a:path_action_pairs for [paths, Action] in a:paths_action_pairs
if name =~# (path . '.\{-}\.java\=$') for path in paths
call Action() if name =~# (path . '.\{-}\.java\=$')
break call Action()
endif return
endif
endfor
endfor endfor
endfunction endfunction
let s:dispatcher = printf('call s:DispatchAction(%s) | ', let s:dir_cnt = min([
\ string([[g:spotbugs_properties.sourceDirPath, \ len(s:SpotBugsGetProperties().sourceDirPath),
\ g:spotbugs_properties.PreCompilerAction], \ len(s:SpotBugsGetProperties().classDirPath)])
\ [g:spotbugs_properties.testSourceDirPath, let s:test_dir_cnt = min([
\ g:spotbugs_properties.PreCompilerTestAction]])) \ len(s:SpotBugsGetProperties().testSourceDirPath),
\ len(s:SpotBugsGetProperties().testClassDirPath)])
" Do not break up path pairs with filtering!
let s:dispatcher = printf('call s:DispatchAction(%s)',
\ string([[s:SpotBugsGetProperties().sourceDirPath[0 : s:dir_cnt - 1],
\ s:SpotBugsGetProperties().PreCompilerAction],
\ [s:SpotBugsGetProperties().testSourceDirPath[0 : s:test_dir_cnt - 1],
\ s:SpotBugsGetProperties().PreCompilerTestAction]]))
unlet s:test_dir_cnt s:dir_cnt
endif
if exists("s:dispatcher")
function! s:ExecuteActions(pre_action, post_action) abort
try
execute a:pre_action
catch /\<E42:/
execute a:post_action
endtry
endfunction
endif endif
if s:request if s:request
if exists("b:spotbugs_syntax_once") if exists("b:spotbugs_syntax_once") || empty(join(getline(1, 8), ''))
let s:actions = [{'event': 'BufWritePost'}] let s:actions = [{'event': 'User'}]
else else
" XXX: Handle multiple FileType events when vimrc contains more " XXX: Handle multiple FileType events when vimrc contains more
" than one filetype setting for the language, e.g.: " than one filetype setting for the language, e.g.:
@ -157,20 +249,25 @@ if exists("g:spotbugs_properties") &&
let s:actions = [{ let s:actions = [{
\ 'event': 'Syntax', \ 'event': 'Syntax',
\ 'once': 1, \ 'once': 1,
\ }, { \ }, {
\ 'event': 'BufWritePost', \ 'event': 'User',
\ }] \ }]
endif endif
for s:idx in range(len(s:actions)) for s:idx in range(len(s:actions))
if s:request == 7 || s:request == 6 || s:request == 5 if s:request == 7 || s:request == 6 || s:request == 5
let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs | ' . let s:actions[s:idx].cmd = printf('call s:ExecuteActions(%s, %s)',
\ 'call g:spotbugs_properties.PostCompilerAction()' \ string(s:dispatcher),
\ string(printf('compiler spotbugs | call call(%s, [])',
\ string(s:SpotBugsGetProperties().PostCompilerAction))))
elseif s:request == 4 elseif s:request == 4
let s:actions[s:idx].cmd = 'compiler spotbugs | ' . let s:actions[s:idx].cmd = printf(
\ 'call g:spotbugs_properties.PostCompilerAction()' \ 'compiler spotbugs | call call(%s, [])',
\ string(s:SpotBugsGetProperties().PostCompilerAction))
elseif s:request == 3 || s:request == 2 || s:request == 1 elseif s:request == 3 || s:request == 2 || s:request == 1
let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs' let s:actions[s:idx].cmd = printf('call s:ExecuteActions(%s, %s)',
\ string(s:dispatcher),
\ string('compiler spotbugs'))
else else
let s:actions[s:idx].cmd = '' let s:actions[s:idx].cmd = ''
endif endif
@ -182,30 +279,39 @@ if exists("g:spotbugs_properties") &&
endif endif
" The events are defined in s:actions. " The events are defined in s:actions.
silent! autocmd! java_spotbugs BufWritePost <buffer> silent! autocmd! java_spotbugs User <buffer>
silent! autocmd! java_spotbugs Syntax <buffer> silent! autocmd! java_spotbugs Syntax <buffer>
for s:action in s:actions for s:action in s:actions
execute printf('autocmd java_spotbugs %s <buffer> %s', if has_key(s:action, 'once')
execute printf('autocmd java_spotbugs %s <buffer> ' .
\ 'call s:ExecuteActionOnce(%s, %s)',
\ s:action.event, \ s:action.event,
\ s:action.cmd . (has_key(s:action, 'once') \ string(printf('autocmd! java_spotbugs %s <buffer>',
\ ? printf(' | autocmd! java_spotbugs %s <buffer>', \ s:action.event)),
\ s:action.event) \ string(s:action.cmd))
\ : '')) else
execute printf('autocmd java_spotbugs %s <buffer> %s',
\ s:action.event,
\ s:action.cmd)
endif
endfor endfor
unlet! s:action s:actions s:idx s:dispatcher unlet! s:action s:actions s:idx s:dispatcher
endif endif
unlet s:request delfunction s:SpotBugsGetProperties
delfunction s:SpotBugsHasProperty
delfunction s:SpotBugsGetProperty
unlet! s:request s:spotbugs_properties_scope
endif endif
function! JavaFileTypeCleanUp() abort function! JavaFileTypeCleanUp() abort
setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr< setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr<
unlet! b:browsefilter unlet! b:browsefilter
" The concatenated removals may be misparsed as a BufWritePost autocmd. " The concatenated removals may be misparsed as a User autocmd.
silent! autocmd! java_spotbugs BufWritePost <buffer> silent! autocmd! java_spotbugs User <buffer>
silent! autocmd! java_spotbugs Syntax <buffer> silent! autocmd! java_spotbugs Syntax <buffer>
endfunction endfunction
@ -232,14 +338,16 @@ if exists("s:zip_func_upgradable")
endif endif
if exists("*s:DispatchAction") if exists("*s:DispatchAction")
def! s:DispatchAction(path_action_pairs: list<list<any>>) def! s:DispatchAction(paths_action_pairs: list<list<any>>)
const name: string = expand('%:p') const name: string = expand('%:p')
for [path: string, Action: func: any] in path_action_pairs for [paths: list<string>, Action: func: any] in paths_action_pairs
if name =~# (path .. '.\{-}\.java\=$') for path in paths
Action() if name =~# (path .. '.\{-}\.java\=$')
break Action()
endif return
endif
endfor
endfor endfor
enddef enddef
endif endif

View File

@ -18,6 +18,8 @@ func Test_compiler()
endif endif
e Xfoo.pl e Xfoo.pl
" Play nice with other tests.
defer setqflist([])
compiler perl compiler perl
call assert_equal('perl', b:current_compiler) call assert_equal('perl', b:current_compiler)
call assert_fails('let g:current_compiler', 'E121:') call assert_fails('let g:current_compiler', 'E121:')
@ -91,6 +93,7 @@ func s:SpotBugsParseFilterMakePrg(dirname, makeprg)
endif endif
let offset += 1 + strlen('-sourcepath') let offset += 1 + strlen('-sourcepath')
let result.sourcepath = matchstr(strpart(a:makeprg, offset), '.\{-}\ze[ \t]') let result.sourcepath = matchstr(strpart(a:makeprg, offset), '.\{-}\ze[ \t]')
let offset += 1 + strlen(result.sourcepath)
" Get the class file arguments, dropping the pathname prefix. " Get the class file arguments, dropping the pathname prefix.
let offset = stridx(a:makeprg, a:dirname, offset) let offset = stridx(a:makeprg, a:dirname, offset)
@ -140,7 +143,8 @@ func Test_compiler_spotbugs_makeprg()
" THE EXPECTED RESULTS. " THE EXPECTED RESULTS.
let results = {} let results = {}
let results['Xspotbugs/src/tests/𐌂1.java'] = { let results['Xspotbugs/src/tests/𐌂1.java'] = {
\ 'sourcepath': '%:p:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/𐌂1.java',
\ ':p:h:S')},
\ 'classfiles': sort([ \ 'classfiles': sort([
\ 'Xspotbugs/tests/𐌂1$1.class', \ 'Xspotbugs/tests/𐌂1$1.class',
\ 'Xspotbugs/tests/𐌂1$1𐌉𐌉1.class', \ 'Xspotbugs/tests/𐌂1$1𐌉𐌉1.class',
@ -153,11 +157,12 @@ func Test_compiler_spotbugs_makeprg()
\ } \ }
" No class file for an empty source file even with "-Xpkginfo:always". " No class file for an empty source file even with "-Xpkginfo:always".
let results['Xspotbugs/src/tests/package-info.java'] = { let results['Xspotbugs/src/tests/package-info.java'] = {
\ 'sourcepath': '', \ 'Sourcepath': {-> ''},
\ 'classfiles': [], \ 'classfiles': [],
\ } \ }
let results['Xspotbugs/src/tests/α/𐌂1.java'] = { let results['Xspotbugs/src/tests/α/𐌂1.java'] = {
\ 'sourcepath': '%:p:h:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/𐌂1.java',
\ ':p:h:h:S')},
\ 'classfiles': sort([ \ 'classfiles': sort([
\ 'Xspotbugs/tests/α/𐌂1$1.class', \ 'Xspotbugs/tests/α/𐌂1$1.class',
\ 'Xspotbugs/tests/α/𐌂1$1𐌉𐌉1.class', \ 'Xspotbugs/tests/α/𐌂1$1𐌉𐌉1.class',
@ -169,11 +174,13 @@ func Test_compiler_spotbugs_makeprg()
\ 'Xspotbugs/tests/α/𐌂2.class']), \ 'Xspotbugs/tests/α/𐌂2.class']),
\ } \ }
let results['Xspotbugs/src/tests/α/package-info.java'] = { let results['Xspotbugs/src/tests/α/package-info.java'] = {
\ 'sourcepath': '%:p:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/package-info.java',
\ ':p:h:S')},
\ 'classfiles': ['Xspotbugs/tests/α/package-info.class'], \ 'classfiles': ['Xspotbugs/tests/α/package-info.class'],
\ } \ }
let results['Xspotbugs/src/tests/α/β/𐌂1.java'] = { let results['Xspotbugs/src/tests/α/β/𐌂1.java'] = {
\ 'sourcepath': '%:p:h:h:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/𐌂1.java',
\ ':p:h:h:h:S')},
\ 'classfiles': sort([ \ 'classfiles': sort([
\ 'Xspotbugs/tests/α/β/𐌂1$1.class', \ 'Xspotbugs/tests/α/β/𐌂1$1.class',
\ 'Xspotbugs/tests/α/β/𐌂1$1𐌉𐌉1.class', \ 'Xspotbugs/tests/α/β/𐌂1$1𐌉𐌉1.class',
@ -185,11 +192,13 @@ func Test_compiler_spotbugs_makeprg()
\ 'Xspotbugs/tests/α/β/𐌂2.class']), \ 'Xspotbugs/tests/α/β/𐌂2.class']),
\ } \ }
let results['Xspotbugs/src/tests/α/β/package-info.java'] = { let results['Xspotbugs/src/tests/α/β/package-info.java'] = {
\ 'sourcepath': '%:p:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/package-info.java',
\ ':p:h:S')},
\ 'classfiles': ['Xspotbugs/tests/α/β/package-info.class'], \ 'classfiles': ['Xspotbugs/tests/α/β/package-info.class'],
\ } \ }
let results['Xspotbugs/src/tests/α/β/γ/𐌂1.java'] = { let results['Xspotbugs/src/tests/α/β/γ/𐌂1.java'] = {
\ 'sourcepath': '%:p:h:h:h:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/𐌂1.java',
\ ':p:h:h:h:h:S')},
\ 'classfiles': sort([ \ 'classfiles': sort([
\ 'Xspotbugs/tests/α/β/γ/𐌂1$1.class', \ 'Xspotbugs/tests/α/β/γ/𐌂1$1.class',
\ 'Xspotbugs/tests/α/β/γ/𐌂1$1𐌉𐌉1.class', \ 'Xspotbugs/tests/α/β/γ/𐌂1$1𐌉𐌉1.class',
@ -201,11 +210,13 @@ func Test_compiler_spotbugs_makeprg()
\ 'Xspotbugs/tests/α/β/γ/𐌂2.class']), \ 'Xspotbugs/tests/α/β/γ/𐌂2.class']),
\ } \ }
let results['Xspotbugs/src/tests/α/β/γ/package-info.java'] = { let results['Xspotbugs/src/tests/α/β/γ/package-info.java'] = {
\ 'sourcepath': '%:p:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/package-info.java',
\ ':p:h:S')},
\ 'classfiles': ['Xspotbugs/tests/α/β/γ/package-info.class'], \ 'classfiles': ['Xspotbugs/tests/α/β/γ/package-info.class'],
\ } \ }
let results['Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java'] = { let results['Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java'] = {
\ 'sourcepath': '%:p:h:h:h:h:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java',
\ ':p:h:h:h:h:h:S')},
\ 'classfiles': sort([ \ 'classfiles': sort([
\ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1.class', \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1.class',
\ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1𐌉𐌉1.class', \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1𐌉𐌉1.class',
@ -217,14 +228,15 @@ func Test_compiler_spotbugs_makeprg()
\ 'Xspotbugs/tests/α/β/γ/δ/𐌂2.class']), \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2.class']),
\ } \ }
let results['Xspotbugs/src/tests/α/β/γ/δ/package-info.java'] = { let results['Xspotbugs/src/tests/α/β/γ/δ/package-info.java'] = {
\ 'sourcepath': '%:p:h:S', \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/δ/package-info.java',
\ ':p:h:S')},
\ 'classfiles': ['Xspotbugs/tests/α/β/γ/δ/package-info.class'], \ 'classfiles': ['Xspotbugs/tests/α/β/γ/δ/package-info.class'],
\ } \ }
" MAKE CLASS FILES DISCOVERABLE! " MAKE CLASS FILES DISCOVERABLE!
let g:spotbugs_properties = { let g:spotbugs_properties = {
\ 'sourceDirPath': 'src/tests', \ 'sourceDirPath': ['src/tests'],
\ 'classDirPath': 'tests', \ 'classDirPath': ['tests'],
\ } \ }
call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'sourcepath')) call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'sourcepath'))
@ -249,20 +261,22 @@ func Test_compiler_spotbugs_makeprg()
let package_file = src_dir .. 'package-info.java' let package_file = src_dir .. 'package-info.java'
call writefile([package], src_dir .. 'package-info.java') call writefile([package], src_dir .. 'package-info.java')
for s in ['on', 'off'] " Note that using "off" for the first _outer_ iteration is preferable
" because only then "hlexists()" may be 0 (see "compiler/spotbugs.vim").
for s in ['off', 'on']
execute 'syntax ' .. s execute 'syntax ' .. s
execute 'edit ' .. type_file execute 'edit ' .. type_file
compiler spotbugs compiler spotbugs
let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg) let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg)
call assert_equal(results[type_file].sourcepath, result.sourcepath) call assert_equal(results[type_file].Sourcepath(), result.sourcepath)
call assert_equal(results[type_file].classfiles, result.classfiles) call assert_equal(results[type_file].classfiles, result.classfiles)
bwipeout bwipeout
execute 'edit ' .. package_file execute 'edit ' .. package_file
compiler spotbugs compiler spotbugs
let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg) let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg)
call assert_equal(results[package_file].sourcepath, result.sourcepath) call assert_equal(results[package_file].Sourcepath(), result.sourcepath)
call assert_equal(results[package_file].classfiles, result.classfiles) call assert_equal(results[package_file].classfiles, result.classfiles)
bwipeout bwipeout
endfor endfor
@ -271,4 +285,364 @@ func Test_compiler_spotbugs_makeprg()
let &shellslash = save_shellslash let &shellslash = save_shellslash
endfunc endfunc
func s:SpotBugsBeforeFileTypeTryPluginAndClearCache(state)
" Ponder over "extend(spotbugs#DefaultProperties(), g:spotbugs_properties)"
" in "ftplugin/java.vim".
let g:spotbugs#state = a:state
runtime autoload/spotbugs.vim
endfunc
func Test_compiler_spotbugs_properties()
let save_shellslash = &shellslash
set shellslash
setlocal makeprg=
filetype plugin on
call assert_true(mkdir('Xspotbugs/src', 'pR'))
call assert_true(mkdir('Xspotbugs/tests', 'pR'))
let type_file = 'Xspotbugs/src/𐌄.java'
let test_file = 'Xspotbugs/tests/𐌄$.java'
call writefile(['enum 𐌄{}'], type_file)
call writefile(['class 𐌄${}'], test_file)
" TEST INTEGRATION WITH A BOGUS COMPILER PLUGIN.
if !filereadable($VIMRUNTIME .. '/compiler/foo.vim') && !executable('foo')
let g:spotbugs_properties = {'compiler': 'foo'}
" XXX: In case this "if" block is no longer first.
call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({
\ 'compiler': g:spotbugs_properties.compiler,
\ })
execute 'edit ' .. type_file
call assert_equal('java', &l:filetype)
" This variable will indefinitely keep the compiler name.
call assert_equal('foo', g:spotbugs#state.compiler)
" The "compiler" entry should be gone after FileType and default entries
" should only appear for a supported compiler.
call assert_false(has_key(g:spotbugs_properties, 'compiler'))
call assert_true(empty(g:spotbugs_properties))
" Query default implementations.
call assert_true(exists('*spotbugs#DefaultProperties'))
call assert_true(exists('*spotbugs#DefaultPreCompilerAction'))
call assert_true(exists('*spotbugs#DefaultPreCompilerTestAction'))
call assert_true(empty(spotbugs#DefaultProperties()))
" Get a ":message".
redir => out
call spotbugs#DefaultPreCompilerAction()
redir END
call assert_equal('Not supported: "foo"', out[stridx(out, 'Not') :])
" Get a ":message".
redir => out
call spotbugs#DefaultPreCompilerTestAction()
redir END
call assert_equal('Not supported: "foo"', out[stridx(out, 'Not') :])
" No ":autocmd"s without one of "PreCompiler*Action", "PostCompilerAction".
call assert_false(exists('#java_spotbugs'))
bwipeout
endif
let s:spotbugs_results = {
\ 'preActionDone': 0,
\ 'preTestActionDone': 0,
\ 'preTestLocalActionDone': 0,
\ 'postActionDone': 0,
\ 'preCommandArguments': '',
\ 'preTestCommandArguments': '',
\ 'postCommandArguments': '',
\ }
defer execute('unlet s:spotbugs_results')
func! g:SpotBugsPreAction() abort
let s:spotbugs_results.preActionDone = 1
" XXX: Notify the spotbugs compiler about success or failure.
cc
endfunc
defer execute('delfunction g:SpotBugsPreAction')
func! g:SpotBugsPreTestAction() abort
let s:spotbugs_results.preTestActionDone = 1
" XXX: Let see compilation fail.
throw 'Oops'
endfunc
defer execute('delfunction g:SpotBugsPreTestAction')
func! g:SpotBugsPreTestLocalAction() abort
let s:spotbugs_results.preTestLocalActionDone = 1
" XXX: Notify the spotbugs compiler about success or failure.
cc
endfunc
defer execute('delfunction g:SpotBugsPreTestLocalAction')
func! g:SpotBugsPostAction() abort
let s:spotbugs_results.postActionDone = 1
endfunc
defer execute('delfunction g:SpotBugsPostAction')
func! g:SpotBugsPreCommand(arguments) abort
let s:spotbugs_results.preActionDone = 1
let s:spotbugs_results.preCommandArguments = a:arguments
" XXX: Notify the spotbugs compiler about success or failure.
cc
endfunc
defer execute('delfunction g:SpotBugsPreCommand')
func! g:SpotBugsPreTestCommand(arguments) abort
let s:spotbugs_results.preTestActionDone = 1
let s:spotbugs_results.preTestCommandArguments = a:arguments
" XXX: Notify the spotbugs compiler about success or failure.
cc
endfunc
defer execute('delfunction g:SpotBugsPreTestCommand')
func! g:SpotBugsPostCommand(arguments) abort
let s:spotbugs_results.postActionDone = 1
let s:spotbugs_results.postCommandArguments = a:arguments
endfunc
defer execute('delfunction g:SpotBugsPostCommand')
" TEST INTEGRATION WITH A SUPPORTED COMPILER PLUGIN.
if filereadable($VIMRUNTIME .. '/compiler/maven.vim')
if !executable('mvn')
if has('win32')
" This is what ":help executable()" suggests.
call writefile([], 'Xspotbugs/mvn.exe')
else
let $PATH = 'Xspotbugs:' .. $PATH
call writefile([], 'Xspotbugs/mvn')
call setfperm('Xspotbugs/mvn', 'rwx------')
endif
endif
let g:spotbugs_properties = {
\ 'compiler': 'maven',
\ 'PreCompilerAction': function('g:SpotBugsPreAction'),
\ 'PreCompilerTestAction': function('g:SpotBugsPreTestAction'),
\ 'PostCompilerAction': function('g:SpotBugsPostAction'),
\ }
" XXX: In case this is a runner-up ":edit".
call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({
\ 'compiler': g:spotbugs_properties.compiler,
\ })
execute 'edit ' .. type_file
call assert_equal('java', &l:filetype)
call assert_equal('maven', g:spotbugs#state.compiler)
call assert_false(has_key(g:spotbugs_properties, 'compiler'))
call assert_false(empty(g:spotbugs_properties))
" Query default implementations.
call assert_true(exists('*spotbugs#DefaultProperties'))
call assert_equal(sort([
\ 'PreCompilerAction',
\ 'PreCompilerTestAction',
\ 'PostCompilerAction',
\ 'sourceDirPath',
\ 'classDirPath',
\ 'testSourceDirPath',
\ 'testClassDirPath',
\ ]),
\ sort(keys(spotbugs#DefaultProperties())))
" Some ":autocmd"s with one of "PreCompiler*Action", "PostCompilerAction".
call assert_true(exists('#java_spotbugs'))
call assert_true(exists('#java_spotbugs#Syntax'))
call assert_true(exists('#java_spotbugs#User'))
call assert_equal(2, exists(':SpotBugsDefineBufferAutocmd'))
" SpotBugsDefineBufferAutocmd SigUSR1 User SigUSR1 User SigUSR1 User
" call assert_true(exists('#java_spotbugs#SigUSR1'))
SpotBugsDefineBufferAutocmd Signal User Signal User Signal User
call assert_true(exists('#java_spotbugs#Signal'))
call assert_true(exists('#java_spotbugs#Syntax'))
call assert_true(exists('#java_spotbugs#User'))
call assert_equal(2, exists(':SpotBugsRemoveBufferAutocmd'))
" SpotBugsRemoveBufferAutocmd SigUSR1 User SigUSR1 User UserGettingBored
" call assert_false(exists('#java_spotbugs#SigUSR1'))
SpotBugsRemoveBufferAutocmd Signal User Signal User UserGettingBored
call assert_false(exists('#java_spotbugs#Signal'))
call assert_true(exists('#java_spotbugs#Syntax'))
call assert_true(exists('#java_spotbugs#User'))
let s:spotbugs_results.preActionDone = 0
let s:spotbugs_results.preTestActionDone = 0
let s:spotbugs_results.postActionDone = 0
doautocmd java_spotbugs Syntax
call assert_false(exists('#java_spotbugs#Syntax'))
" No match: "type_file !~# 'src/main/java'".
call assert_false(s:spotbugs_results.preActionDone)
" No match: "type_file !~# 'src/test/java'".
call assert_false(s:spotbugs_results.preTestActionDone)
" No pre-match, no post-action.
call assert_false(s:spotbugs_results.postActionDone)
" Without a match, confirm that ":compiler spotbugs" has NOT run.
call assert_true(empty(&l:makeprg))
let s:spotbugs_results.preActionDone = 0
let s:spotbugs_results.preTestActionDone = 0
let s:spotbugs_results.postActionDone = 0
" Update path entries. (Note that we cannot use just "src" because there
" is another "src" directory nearer the filesystem root directory, i.e.
" "vim/vim/src/testdir/Xspotbugs/src", and "s:DispatchAction()" (see
" "ftplugin/java.vim") will match "vim/vim/src/testdir/Xspotbugs/tests"
" against "src".)
let g:spotbugs_properties.sourceDirPath = ['Xspotbugs/src']
let g:spotbugs_properties.classDirPath = ['Xspotbugs/src']
let g:spotbugs_properties.testSourceDirPath = ['tests']
let g:spotbugs_properties.testClassDirPath = ['tests']
doautocmd java_spotbugs User
" No match: "type_file !~# 'src/main/java'" (with old "*DirPath" values
" cached).
call assert_false(s:spotbugs_results.preActionDone)
" No match: "type_file !~# 'src/test/java'" (with old "*DirPath" values
" cached).
call assert_false(s:spotbugs_results.preTestActionDone)
" No pre-match, no post-action.
call assert_false(s:spotbugs_results.postActionDone)
" Without a match, confirm that ":compiler spotbugs" has NOT run.
call assert_true(empty(&l:makeprg))
let s:spotbugs_results.preActionDone = 0
let s:spotbugs_results.preTestActionDone = 0
let s:spotbugs_results.postActionDone = 0
" XXX: Re-build ":autocmd"s from scratch with new values applied.
doautocmd FileType
call assert_true(exists('b:spotbugs_syntax_once'))
doautocmd java_spotbugs User
" A match: "type_file =~# 'Xspotbugs/src'" (with new "*DirPath" values
" cached).
call assert_true(s:spotbugs_results.preActionDone)
" No match: "type_file !~# 'tests'" (with new "*DirPath" values cached).
call assert_false(s:spotbugs_results.preTestActionDone)
" For a pre-match, a post-action.
call assert_true(s:spotbugs_results.postActionDone)
" With a match, confirm that ":compiler spotbugs" has run.
if has('win32')
call assert_match('^spotbugs\.bat\s', &l:makeprg)
else
call assert_match('^spotbugs\s', &l:makeprg)
endif
bwipeout
setlocal makeprg=
let s:spotbugs_results.preActionDone = 0
let s:spotbugs_results.preTestActionDone = 0
let s:spotbugs_results.preTestLocalActionDone = 0
let s:spotbugs_results.postActionDone = 0
execute 'edit ' .. test_file
" Prepare a buffer-local, incomplete variant of properties, relying on
" "ftplugin/java.vim" to take care of merging in unique entries, if any,
" from "g:spotbugs_properties".
let b:spotbugs_properties = {
\ 'PreCompilerTestAction': function('g:SpotBugsPreTestLocalAction'),
\ }
call assert_equal('java', &l:filetype)
call assert_true(exists('#java_spotbugs'))
call assert_true(exists('#java_spotbugs#Syntax'))
call assert_true(exists('#java_spotbugs#User'))
call assert_fails('doautocmd java_spotbugs Syntax', 'Oops')
call assert_false(exists('#java_spotbugs#Syntax'))
" No match: "test_file !~# 'Xspotbugs/src'".
call assert_false(s:spotbugs_results.preActionDone)
" A match: "test_file =~# 'tests'".
call assert_true(s:spotbugs_results.preTestActionDone)
call assert_false(s:spotbugs_results.preTestLocalActionDone)
" No action after pre-failure (the thrown "Oops" doesn't qualify for ":cc").
call assert_false(s:spotbugs_results.postActionDone)
" No ":compiler spotbugs" will be run after pre-failure.
call assert_true(empty(&l:makeprg))
let s:spotbugs_results.preActionDone = 0
let s:spotbugs_results.preTestActionDone = 0
let s:spotbugs_results.preTestLocalActionDone = 0
let s:spotbugs_results.postActionDone = 0
" XXX: Re-build ":autocmd"s from scratch with buffer-local values applied.
doautocmd FileType
call assert_true(exists('b:spotbugs_syntax_once'))
doautocmd java_spotbugs User
" No match: "test_file !~# 'Xspotbugs/src'".
call assert_false(s:spotbugs_results.preActionDone)
" A match: "test_file =~# 'tests'".
call assert_true(s:spotbugs_results.preTestLocalActionDone)
call assert_false(s:spotbugs_results.preTestActionDone)
" For a pre-match, a post-action.
call assert_true(s:spotbugs_results.postActionDone)
" With a match, confirm that ":compiler spotbugs" has run.
if has('win32')
call assert_match('^spotbugs\.bat\s', &l:makeprg)
else
call assert_match('^spotbugs\s', &l:makeprg)
endif
setlocal makeprg=
let s:spotbugs_results.preActionDone = 0
let s:spotbugs_results.preTestActionDone = 0
let s:spotbugs_results.preTestLocalActionDone = 0
let s:spotbugs_results.postActionDone = 0
let s:spotbugs_results.preCommandArguments = ''
let s:spotbugs_results.preTestCommandArguments = ''
let s:spotbugs_results.postCommandArguments = ''
" XXX: Compose the assigned "*Command"s with the default Maven "*Action"s.
let b:spotbugs_properties = {
\ 'compiler': 'maven',
\ 'DefaultPreCompilerTestCommand': function('g:SpotBugsPreTestCommand'),
\ 'DefaultPreCompilerCommand': function('g:SpotBugsPreCommand'),
\ 'DefaultPostCompilerCommand': function('g:SpotBugsPostCommand'),
\ 'sourceDirPath': ['Xspotbugs/src'],
\ 'classDirPath': ['Xspotbugs/src'],
\ 'testSourceDirPath': ['tests'],
\ 'testClassDirPath': ['tests'],
\ }
unlet g:spotbugs_properties
" XXX: Re-build ":autocmd"s from scratch with buffer-local values applied.
call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({
\ 'compiler': b:spotbugs_properties.compiler,
\ 'commands': {
\ 'DefaultPreCompilerTestCommand':
\ b:spotbugs_properties.DefaultPreCompilerTestCommand,
\ 'DefaultPreCompilerCommand':
\ b:spotbugs_properties.DefaultPreCompilerCommand,
\ 'DefaultPostCompilerCommand':
\ b:spotbugs_properties.DefaultPostCompilerCommand,
\ },
\ })
doautocmd FileType
call assert_equal('maven', g:spotbugs#state.compiler)
call assert_equal(sort([
\ 'DefaultPreCompilerTestCommand',
\ 'DefaultPreCompilerCommand',
\ 'DefaultPostCompilerCommand',
\ ]),
\ sort(keys(g:spotbugs#state.commands)))
call assert_true(exists('b:spotbugs_syntax_once'))
doautocmd java_spotbugs User
" No match: "test_file !~# 'Xspotbugs/src'".
call assert_false(s:spotbugs_results.preActionDone)
call assert_true(empty(s:spotbugs_results.preCommandArguments))
" A match: "test_file =~# 'tests'".
call assert_true(s:spotbugs_results.preTestActionDone)
call assert_equal('test-compile', s:spotbugs_results.preTestCommandArguments)
" For a pre-match, a post-action.
call assert_true(s:spotbugs_results.postActionDone)
call assert_equal('%:S', s:spotbugs_results.postCommandArguments)
" With a match, confirm that ":compiler spotbugs" has run.
if has('win32')
call assert_match('^spotbugs\.bat\s', &l:makeprg)
else
call assert_match('^spotbugs\s', &l:makeprg)
endif
bwipeout
setlocal makeprg=
endif
filetype plugin off
setlocal makeprg=
let &shellslash = save_shellslash
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab