]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
runtime(compiler): include spotbugs Java linter
authorKonfekt <Konfekt@users.noreply.github.com>
Thu, 28 Nov 2024 20:06:09 +0000 (21:06 +0100)
committerChristian Brabandt <cb@256bit.org>
Thu, 28 Nov 2024 20:06:09 +0000 (21:06 +0100)
closes: #16001

Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
Signed-off-by: Konfekt <Konfekt@users.noreply.github.com>
Signed-off-by: Aliaksei Budavei <0x000c70@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/autoload/spotbugs.vim [new file with mode: 0644]
runtime/compiler/javac.vim
runtime/compiler/maven.vim
runtime/compiler/spotbugs.vim [new file with mode: 0644]
runtime/doc/quickfix.txt
runtime/doc/tags
runtime/ftplugin/java.vim

diff --git a/runtime/autoload/spotbugs.vim b/runtime/autoload/spotbugs.vim
new file mode 100644 (file)
index 0000000..9161395
--- /dev/null
@@ -0,0 +1,250 @@
+" Default pre- and post-compiler actions for SpotBugs
+" Maintainers:  @konfekt and @zzzyxwvut
+" Last Change:  2024 Nov 27
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+if v:version > 900
+
+  function! spotbugs#DeleteClassFiles() abort
+    if !exists('b:spotbugs_class_files')
+      return
+    endif
+
+    for pathname in b:spotbugs_class_files
+      let classname = pathname =~# "^'.\\+\\.class'$"
+          \ ? eval(pathname)
+          \ : pathname
+
+      if classname =~# '\.class$' && filereadable(classname)
+        " Since v9.0.0795.
+        let octad = readblob(classname, 0, 8)
+
+        " Test the magic number and the major version number (45 for v1.0).
+        " Since v9.0.2027.
+        if len(octad) == 8 && octad[0 : 3] == 0zcafe.babe &&
+              \ or((octad[6] << 8), octad[7]) >= 45
+          echomsg printf('Deleting %s: %d', classname, delete(classname))
+        endif
+      endif
+    endfor
+
+    let b:spotbugs_class_files = []
+  endfunction
+
+else
+
+  function! s:DeleteClassFilesWithNewLineCodes(classname) abort
+    " The distribution of "0a"s in class file versions 2560 and 2570:
+    "
+    " 0zca.fe.ba.be.00.00.0a.00    0zca.fe.ba.be.00.00.0a.0a
+    " 0zca.fe.ba.be.00.0a.0a.00    0zca.fe.ba.be.00.0a.0a.0a
+    " 0zca.fe.ba.be.0a.00.0a.00    0zca.fe.ba.be.0a.00.0a.0a
+    " 0zca.fe.ba.be.0a.0a.0a.00    0zca.fe.ba.be.0a.0a.0a.0a
+    let numbers = [0, 0, 0, 0, 0, 0, 0, 0]
+    let offset = 0
+    let lines = readfile(a:classname, 'b', 4)
+
+    " Track NL byte counts to handle files of less than 8 bytes.
+    let nl_cnt = len(lines)
+    " Track non-NL byte counts for "0zca.fe.ba.be.0a.0a.0a.0a".
+    let non_nl_cnt = 0
+
+    for line in lines
+      for idx in range(strlen(line))
+        " Remap NLs to Nuls.
+        let numbers[offset] = (line[idx] == "\n") ? 0 : char2nr(line[idx]) % 256
+        let non_nl_cnt += 1
+        let offset += 1
+
+        if offset > 7
+          break
+        endif
+      endfor
+
+      let nl_cnt -= 1
+
+      if offset > 7 || (nl_cnt < 1 && non_nl_cnt > 4)
+        break
+      endif
+
+      " Reclaim NLs.
+      let numbers[offset] = 10
+      let offset += 1
+
+      if offset > 7
+        break
+      endif
+    endfor
+
+    " Test the magic number and the major version number (45 for v1.0).
+    if offset > 7 && numbers[0] == 0xca && numbers[1] == 0xfe &&
+          \ numbers[2] == 0xba && numbers[3] == 0xbe &&
+          \ (numbers[6] * 256 + numbers[7]) >= 45
+      echomsg printf('Deleting %s: %d', a:classname, delete(a:classname))
+    endif
+  endfunction
+
+  function! spotbugs#DeleteClassFiles() abort
+    if !exists('b:spotbugs_class_files')
+      return
+    endif
+
+    let encoding = &encoding
+
+    try
+      set encoding=latin1
+
+      for pathname in b:spotbugs_class_files
+        let classname = pathname =~# "^'.\\+\\.class'$"
+            \ ? eval(pathname)
+            \ : pathname
+
+        if classname =~# '\.class$' && filereadable(classname)
+          let line = get(readfile(classname, 'b', 1), 0, '')
+          let length = strlen(line)
+
+          " Test the magic number and the major version number (45 for v1.0).
+          if length > 3 && line[0 : 3] == "\xca\xfe\xba\xbe"
+            if length > 7 && ((line[6] == "\n" ? 0 : char2nr(line[6]) % 256) * 256 +
+                    \ (line[7] == "\n" ? 0 : char2nr(line[7]) % 256)) >= 45
+              echomsg printf('Deleting %s: %d', classname, delete(classname))
+            else
+              call s:DeleteClassFilesWithNewLineCodes(classname)
+            endif
+          endif
+        endif
+      endfor
+    finally
+      let &encoding = encoding
+    endtry
+
+    let b:spotbugs_class_files = []
+  endfunction
+
+endif
+
+function! spotbugs#DefaultPostCompilerAction() abort
+  " Since v7.4.191.
+  make %:S
+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')
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler maven
+    make compile
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler maven
+    make test-compile
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {
+        \ 'PreCompilerAction':
+            \ function('spotbugs#DefaultPreCompilerAction'),
+        \ 'PreCompilerTestAction':
+            \ function('spotbugs#DefaultPreCompilerTestAction'),
+        \ 'PostCompilerAction':
+            \ function('spotbugs#DefaultPostCompilerAction'),
+        \ 'sourceDirPath':      'src/main/java',
+        \ 'classDirPath':       'target/classes',
+        \ 'testSourceDirPath':  'src/test/java',
+        \ 'testClassDirPath':   'target/test-classes',
+        \ }
+  endfunction
+
+  unlet s:readable s:compiler
+elseif s:readable && s:compiler ==# 'ant' && executable('ant')
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler ant
+    make compile
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler ant
+    make compile-test
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {
+        \ 'PreCompilerAction':
+            \ function('spotbugs#DefaultPreCompilerAction'),
+        \ 'PreCompilerTestAction':
+            \ function('spotbugs#DefaultPreCompilerTestAction'),
+        \ 'PostCompilerAction':
+            \ function('spotbugs#DefaultPostCompilerAction'),
+        \ 'sourceDirPath':      'src',
+        \ 'classDirPath':       'build/classes',
+        \ 'testSourceDirPath':  'test',
+        \ 'testClassDirPath':   'build/test/classes',
+        \ }
+  endfunction
+
+  unlet s:readable s:compiler
+elseif s:readable && s:compiler ==# 'javac' && executable('javac')
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    call spotbugs#DeleteClassFiles()
+    compiler javac
+
+    if get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')) =~ '\s@\S'
+      " Read options and filenames from @options [@sources ...].
+      make
+    else
+      " Let Javac figure out what files to compile.
+      execute 'make ' . join(map(filter(copy(v:argv),
+          \ "v:val =~# '\\.java\\=$'"),
+          \ 'shellescape(v:val)'), ' ')
+    endif
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DefaultPreCompilerAction()
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {
+        \ 'PreCompilerAction':
+            \ function('spotbugs#DefaultPreCompilerAction'),
+        \ 'PreCompilerTestAction':
+            \ function('spotbugs#DefaultPreCompilerTestAction'),
+        \ 'PostCompilerAction':
+            \ function('spotbugs#DefaultPostCompilerAction'),
+        \ }
+  endfunction
+
+  unlet s:readable s:compiler
+else
+
+  function! spotbugs#DefaultPreCompilerAction() abort
+    echomsg printf('Not supported: "%s"', s:compiler)
+  endfunction
+
+  function! spotbugs#DefaultPreCompilerTestAction() abort
+    call spotbugs#DefaultPreCompilerAction()
+  endfunction
+
+  function! spotbugs#DefaultProperties() abort
+    return {}
+  endfunction
+
+  unlet s:readable
+endif
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
+
+" vim: set foldmethod=syntax shiftwidth=2 expandtab:
index 9bd4cdf270f984b9dd95ca665e39f211257b5362..53cd772ed8a02f8f78d864624b2acfb270af9082 100644 (file)
@@ -1,7 +1,7 @@
 " Vim compiler file
 " Compiler:    Java Development Kit Compiler
 " Maintainer:  Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2024 Jun 14
+" Last Change: 2024 Nov 19 (enable local javac_makeprg_params)
 
 if exists("current_compiler")
   finish
@@ -11,11 +11,7 @@ let current_compiler = "javac"
 let s:cpo_save = &cpo
 set cpo&vim
 
-if exists("g:javac_makeprg_params")
-  execute $'CompilerSet makeprg=javac\ {escape(g:javac_makeprg_params, ' \|"')}'
-else
-  CompilerSet makeprg=javac
-endif
+execute $'CompilerSet makeprg=javac\ {escape(get(b:, 'javac_makeprg_params', get(g:, 'javac_makeprg_params', '')), ' \|"')}'
 
 CompilerSet errorformat=%E%f:%l:\ error:\ %m,
                       \%W%f:%l:\ warning:\ %m,
index ef8d8a6fb2955e53e81e286ac805c36ad9a04835..72e74e301da769f55e615adbe7f3a8f80e6d721f 100644 (file)
@@ -14,7 +14,7 @@ if exists("current_compiler")
 endif
 let current_compiler = "maven"
 
-CompilerSet makeprg=mvn\ --batch-mode
+execute $'CompilerSet makeprg=mvn\ --batch-mode\ {escape(get(b:, 'maven_makeprg_params', get(g:, 'maven_makeprg_params', '')), ' \|"')}'
 
 " Error message for POM
 CompilerSet errorformat=[FATAL]\ Non-parseable\ POM\ %f:\ %m%\\s%\\+@%.%#line\ %l\\,\ column\ %c%.%#,
diff --git a/runtime/compiler/spotbugs.vim b/runtime/compiler/spotbugs.vim
new file mode 100644 (file)
index 0000000..72a5084
--- /dev/null
@@ -0,0 +1,189 @@
+" Vim compiler file
+" Compiler:     Spotbugs (Java static checker; needs javac compiled classes)
+" Maintainer:   @konfekt and @zzzyxwvut
+" Last Change:  2024 Nov 27
+
+if exists('g:current_compiler') || bufname() !~# '\.java\=$' || wordcount().chars < 9
+  finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Unfortunately Spotbugs does not output absolute paths, so you need to
+" pass the directory of the files being checked as `-sourcepath` parameter.
+" The regex, auxpath and glob try to include all dependent classes of the
+" current buffer. See https://github.com/spotbugs/spotbugs/issues/856
+
+" FIXME: When "search()" is used with the "e" flag, it makes no _further_
+" progress after claiming an EOL match (i.e. "\_" or "\n", but not "$").
+" XXX: Omit anonymous class declarations
+let s:keywords = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\|package\)\%(\s\|$\)'
+let s:type_names = '\C\<\%(\.\@1<!class\|@\=interface\|enum\|record\)\s*\(\K\k*\)\>'
+" Capture ";" for counting a class file directory (see s:package_dir_heads below)
+let s:package_names = '\C\<package\s*\(\K\%(\k*\.\=\)\+;\)'
+let s:package = ''
+
+if has('syntax') && exists('g:syntax_on') && exists('b:current_syntax') &&
+    \ b:current_syntax == 'java' && hlexists('javaClassDecl')
+
+  function! s:GetDeclaredTypeNames() abort
+    if bufname() =~# '\<\%(module\|package\)-info\.java\=$'
+      return [expand('%:t:r')]
+    endif
+    defer execute('silent! normal! g``')
+    call cursor(1, 1)
+    let type_names = []
+    let lnum = search(s:keywords, 'eW')
+    while lnum > 0
+      let name_attr = synIDattr(synID(lnum, (col('.') - 1), 0), 'name')
+      if name_attr ==# 'javaClassDecl'
+        let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:type_names)
+        if !empty(tokens) | call add(type_names, tokens[1]) | endif
+      elseif name_attr ==# 'javaExternal'
+        let tokens = matchlist(getline(lnum)..getline(lnum + 1), s:package_names)
+        if !empty(tokens) | let s:package = tokens[1] | endif
+      endif
+      let lnum = search(s:keywords, 'eW')
+    endwhile
+    return type_names
+  endfunction
+
+else
+  function! s:GetDeclaredTypeNames() abort
+    if bufname() =~# '\<\%(module\|package\)-info\.java\=$'
+      return [expand('%:t:r')]
+    endif
+    " Undo the unsetting of &hls, see below
+    if &hls
+      defer execute('set hls')
+    endif
+    " Possibly restore the current values for registers '"' and "y", see below
+    defer call('setreg', ['"', getreg('"'), getregtype('"')])
+    defer call('setreg', ['y', getreg('y'), getregtype('y')])
+    defer execute('silent bwipeout')
+    " Copy buffer contents for modification
+    silent %y y
+    new
+    " Apply ":help scratch-buffer" effects and match "$" in Java (generated)
+    " type names (see s:type_names)
+    setlocal iskeyword+=$ buftype=nofile bufhidden=hide noswapfile nohls
+    0put y
+    " Discard text blocks and strings
+    silent keeppatterns %s/\\\@<!"""\_.\{-}\\\@<!"""\|\\"//ge
+    silent keeppatterns %s/".*"//ge
+    " Discard comments
+    silent keeppatterns %s/\/\/.\+$//ge
+    silent keeppatterns %s/\/\*\_.\{-}\*\///ge
+    call cursor(1, 1)
+    let type_names = []
+    let lnum = search(s:keywords, 'eW')
+    while lnum > 0
+      let line = getline(lnum)
+      if line =~# '\<package\>'
+        let tokens = matchlist(line..getline(lnum + 1), s:package_names)
+        if !empty(tokens) | let s:package = tokens[1] | endif
+      else
+        let tokens = matchlist(line..getline(lnum + 1), s:type_names)
+        if !empty(tokens) | call add(type_names, tokens[1]) | endif
+      endif
+      let lnum = search(s:keywords, 'eW')
+    endwhile
+    return type_names
+  endfunction
+endif
+
+if has('win32')
+
+  function! s:GlobClassFiles(src_type_name) abort
+    return glob(a:src_type_name..'$*.class', 1, 1)
+  endfunction
+
+else
+  function! s:GlobClassFiles(src_type_name) abort
+    return glob(a:src_type_name..'\$*.class', 1, 1)
+  endfunction
+endif
+
+if exists('g:spotbugs_properties') &&
+    \ (has_key(g:spotbugs_properties, 'sourceDirPath') &&
+    \ has_key(g:spotbugs_properties, 'classDirPath')) ||
+    \ (has_key(g:spotbugs_properties, 'testSourceDirPath') &&
+    \ has_key(g:spotbugs_properties, 'testClassDirPath'))
+
+function! s:FindClassFiles(src_type_name) abort
+  let class_files = []
+  " Match pairwise the components of source and class pathnames
+  for [src_dir, bin_dir] in filter([
+            \ [get(g:spotbugs_properties, 'sourceDirPath', ''),
+                \ get(g:spotbugs_properties, 'classDirPath', '')],
+            \ [get(g:spotbugs_properties, 'testSourceDirPath', ''),
+                \ get(g:spotbugs_properties, 'testClassDirPath', '')]],
+        \ '!(empty(v:val[0]) || empty(v:val[1]))')
+    " 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
+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')
+    if filereadable(candidate) | call add(class_files, shellescape(candidate)) | endif
+  endfor
+  return class_files
+endfunction
+endif
+
+function! s:CollectClassFiles() abort
+  " Get a platform-independent pathname prefix, cf. "expand('%:p:h')..'/'"
+  let pathname = expand('%:p')
+  let tail_idx = strridx(pathname, expand('%:t'))
+  let src_pathname = strpart(pathname, 0, tail_idx)
+  let all_class_files = []
+  " Get all type names in the current buffer and let the filename globbing
+  " discover inner type names from arbitrary type names
+  for type_name in s:GetDeclaredTypeNames()
+    call extend(all_class_files, s:FindClassFiles(src_pathname..type_name))
+  endfor
+  return all_class_files
+endfunction
+
+" Expose class files for removal etc.
+let b:spotbugs_class_files = s:CollectClassFiles()
+let s:package_dir_heads = repeat(':h', (1 + strlen(substitute(s:package, '[^.;]', '', 'g'))))
+let g:current_compiler = 'spotbugs'
+" CompilerSet makeprg=spotbugs
+let &l:makeprg = 'spotbugs'..(has('win32') ? '.bat' : '')..' '..
+    \ 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 '..
+    \ join(b:spotbugs_class_files, ' ')
+" Emacs expects doubled line numbers
+setlocal errorformat=%f:%l:%*[0-9]\ %m,%f:-%*[0-9]:-%*[0-9]\ %m
+
+" " This compiler is meant to be used for a single buffer only
+" exe 'CompilerSet makeprg='..escape(&l:makeprg, ' \|"')
+" exe 'CompilerSet errorformat='..escape(&l:errorformat, ' \|"')
+
+delfunction s:CollectClassFiles
+delfunction s:FindClassFiles
+delfunction s:GlobClassFiles
+delfunction s:GetDeclaredTypeNames
+let &cpo = s:cpo_save
+unlet s:package_dir_heads s:package s:package_names s:type_names s:keywords s:cpo_save
+
+" vim: set foldmethod=syntax shiftwidth=2 expandtab:
index 671630d66781a7d63384d9ec31d6a77f758adb3f..5d97f793f2f2f0bf7788a614d70fb122167d2b5b 100644 (file)
@@ -1,4 +1,4 @@
-*quickfix.txt*  For Vim version 9.1.  Last change: 2024 Nov 12
+*quickfix.txt*  For Vim version 9.1.  Last change: 2024 Nov 28
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -1331,10 +1331,117 @@ g:compiler_gcc_ignore_unmatched_lines
 JAVAC                                                  *compiler-javac*
 
 Commonly used compiler options can be added to 'makeprg' by setting the
-g:javac_makeprg_params variable.  For example: >
+b/g:javac_makeprg_params variable.  For example: >
 
        let g:javac_makeprg_params = "-Xlint:all -encoding utf-8"
-<
+
+MAVEN                                                  *compiler-maven*
+
+Commonly used compiler options can be added to 'makeprg' by setting the
+b/g:maven_makeprg_params variable.  For example: >
+
+       let g:maven_makeprg_params = "-DskipTests -U -X"
+
+SPOTBUGS                                               *compiler-spotbugs*
+
+SpotBugs is a static analysis tool that can be used to find bugs in Java.
+It scans the Java bytecode of all classes in the currently open buffer.
+(Therefore, `:compiler! spotbugs` is not supported.)
+
+Commonly used compiler options can be added to 'makeprg' by setting the
+"b:" or "g:spotbugs_makeprg_params" variable.  For example: >
+
+       let b:spotbugs_makeprg_params = "-longBugCodes -effort:max -low"
+
+The global default is "-workHard -experimental".
+
+By default, the class files are searched in the directory where the source
+files are placed.  However, typical Java projects use distinct directories
+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
+following properties (using the example of a common Maven project): >
+
+       let g:spotbugs_properties = {
+               \ 'sourceDirPath':      'src/main/java',
+               \ 'classDirPath':       'target/classes',
+               \ 'testSourceDirPath':  'src/test/java',
+               \ 'testClassDirPath':   'target/test-classes',
+       \ }
+
+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
+information.
+
+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
+plugin to the "compiler" key: >
+
+       let g:spotbugs_properties = {
+               \ 'compiler':           'maven',
+       \ }
+
+This single setting is essentially equivalent to all the settings below, with
+the exception made for the "PreCompilerAction" and "PreCompilerTestAction"
+values: their listed |Funcref|s will obtain no-op implementations whereas the
+implicit Funcrefs of the "compiler" key will obtain the requested defaults if
+available. >
+
+       let g:spotbugs_properties = {
+               \ 'PreCompilerAction':
+                       \ function('spotbugs#DefaultPreCompilerAction'),
+               \ 'PreCompilerTestAction':
+                       \ function('spotbugs#DefaultPreCompilerTestAction'),
+               \ 'PostCompilerAction':
+                       \ function('spotbugs#DefaultPostCompilerAction'),
+               \ 'sourceDirPath':      'src/main/java',
+               \ 'classDirPath':       'target/classes',
+               \ 'testSourceDirPath':  'src/test/java',
+               \ 'testClassDirPath':   'target/test-classes',
+       \ }
+
+With default actions, the compiler of choice will attempt to rebuild the class
+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
+the compilation unit of the buffer.
+
+When default actions are not suited to a desired workflow, consider writing
+arbitrary functions yourself and matching their |Funcref|s to the supported
+keys: "PreCompilerAction", "PreCompilerTestAction", and "PostCompilerAction".
+
+The next example re-implements the default pre-compiler actions for a Maven
+project and requests other default Maven settings with the "compiler" entry: >
+
+       function! MavenPreCompilerAction() abort
+               call spotbugs#DeleteClassFiles()
+               compiler maven
+               make compile
+       endfunction
+
+       function! MavenPreCompilerTestAction() abort
+               call spotbugs#DeleteClassFiles()
+               compiler maven
+               make test-compile
+       endfunction
+
+       let g:spotbugs_properties = {
+               \ 'compiler':           'maven',
+               \ 'PreCompilerAction':
+                       \ function('MavenPreCompilerAction'),
+               \ 'PreCompilerTestAction':
+                       \ function('MavenPreCompilerTestAction'),
+       \ }
+
+Note that all entered custom settings will take precedence over the matching
+default settings in "g:spotbugs_properties".
+
+The "g:spotbugs_properties" variable is consulted by the Java filetype plugin
+(|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
+with Java source files.  It could, for example, be set in a project-local
+|vimrc| loaded by [0].
+
+[0] https://github.com/MarcWeber/vim-addon-local-vimrc/
+
 GNU MAKE                                               *compiler-make*
 
 Since the default make program is "make", the compiler plugin for make,
index 68465cc091e542325a9d252d57a61fb5c1079af7..b550550268b39dbab6577d9a3d3ce2ebfd1087dd 100644 (file)
@@ -6565,6 +6565,7 @@ compiler-hpada    ft_ada.txt      /*compiler-hpada*
 compiler-javac quickfix.txt    /*compiler-javac*
 compiler-make  quickfix.txt    /*compiler-make*
 compiler-manx  quickfix.txt    /*compiler-manx*
+compiler-maven quickfix.txt    /*compiler-maven*
 compiler-mypy  quickfix.txt    /*compiler-mypy*
 compiler-pandoc        quickfix.txt    /*compiler-pandoc*
 compiler-perl  quickfix.txt    /*compiler-perl*
@@ -6572,6 +6573,7 @@ compiler-pylint   quickfix.txt    /*compiler-pylint*
 compiler-pyunit        quickfix.txt    /*compiler-pyunit*
 compiler-ruff  quickfix.txt    /*compiler-ruff*
 compiler-select        quickfix.txt    /*compiler-select*
+compiler-spotbugs      quickfix.txt    /*compiler-spotbugs*
 compiler-tex   quickfix.txt    /*compiler-tex*
 compiler-typst quickfix.txt    /*compiler-typst*
 compiler-vaxada        ft_ada.txt      /*compiler-vaxada*
index 55b358374fc4fb5d324965899bfb49a97efda818..6e12fe2fe531a748bbf622bc1d85a1554b442262 100644 (file)
@@ -3,7 +3,7 @@
 " Maintainer:          Aliaksei Budavei <0x000c70 AT gmail DOT com>
 " Former Maintainer:   Dan Sharp
 " Repository:          https://github.com/zzzyxwvut/java-vim.git
-" Last Change:         2024 Sep 26
+" Last Change:         2024 Nov 24
 "                      2024 Jan 14 by Vim Project (browsefilter)
 "                      2024 May 23 by Riley Bruins <ribru17@gmail.com> ('commentstring')
 
@@ -90,10 +90,127 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
     endif
 endif
 
+" The support for pre- and post-compiler actions for SpotBugs.
+if exists("g:spotbugs_properties") && has_key(g:spotbugs_properties, 'compiler')
+    try
+       let spotbugs#compiler = g:spotbugs_properties.compiler
+       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") &&
+           \ filereadable($VIMRUNTIME . '/compiler/spotbugs.vim')
+    let s:request = 0
+
+    if has_key(g:spotbugs_properties, 'PreCompilerAction')
+       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
+    endif
+
+    if (s:request == 3 || s:request == 7) &&
+           \ has_key(g:spotbugs_properties, 'sourceDirPath') &&
+           \ has_key(g:spotbugs_properties, 'testSourceDirPath')
+       function! s:DispatchAction(path_action_pairs) abort
+           let name = expand('%:p')
+
+           for [path, Action] in a:path_action_pairs
+               if name =~# (path . '.\{-}\.java\=$')
+                   call Action()
+                   break
+               endif
+           endfor
+       endfunction
+
+       let s:dispatcher = printf('call s:DispatchAction(%s) | ',
+               \ string([[g:spotbugs_properties.sourceDirPath,
+                           \ g:spotbugs_properties.PreCompilerAction],
+                       \ [g:spotbugs_properties.testSourceDirPath,
+                           \ g:spotbugs_properties.PreCompilerTestAction]]))
+    endif
+
+    if s:request
+       if exists("b:spotbugs_syntax_once")
+           let s:actions = [{'event': 'BufWritePost'}]
+       else
+           " XXX: Handle multiple FileType events when vimrc contains more
+           " than one filetype setting for the language, e.g.:
+           "   :filetype plugin indent on
+           "   :autocmd BufRead,BufNewFile *.java setlocal filetype=java ...
+           " XXX: DO NOT ADD b:spotbugs_syntax_once TO b:undo_ftplugin !
+           let b:spotbugs_syntax_once = 1
+           let s:actions = [{
+                   \ 'event': 'Syntax',
+                   \ 'once': 1,
+                   \ }, {
+                   \ 'event': 'BufWritePost',
+                   \ }]
+       endif
+
+       for s:idx in range(len(s:actions))
+           if s:request == 7 || s:request == 6 || s:request == 5
+               let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs | ' .
+                       \ 'call g:spotbugs_properties.PostCompilerAction()'
+           elseif s:request == 4
+               let s:actions[s:idx].cmd = 'compiler spotbugs | ' .
+                       \ 'call g:spotbugs_properties.PostCompilerAction()'
+           elseif s:request == 3 || s:request == 2 || s:request == 1
+               let s:actions[s:idx].cmd = s:dispatcher . 'compiler spotbugs'
+           else
+               let s:actions[s:idx].cmd = ''
+           endif
+       endfor
+
+       if !exists("#java_spotbugs")
+           augroup java_spotbugs
+           augroup END
+       endif
+
+       " The events are defined in s:actions.
+       silent! autocmd! java_spotbugs BufWritePost <buffer>
+       silent! autocmd! java_spotbugs Syntax <buffer>
+
+       for s:action in s:actions
+           execute printf('autocmd java_spotbugs %s <buffer> %s',
+                   \ s:action.event,
+                   \ s:action.cmd . (has_key(s:action, 'once')
+                           \ ? printf(' | autocmd! java_spotbugs %s <buffer>',
+                                   \ s:action.event)
+                           \ : ''))
+       endfor
+
+       unlet! s:action s:actions s:idx s:dispatcher
+    endif
+
+    unlet s:request
+endif
+
+function! JavaFileTypeCleanUp() abort
+    setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr<
+    unlet! b:browsefilter
+
+    " The concatenated removals may be misparsed as a BufWritePost autocmd.
+    silent! autocmd! java_spotbugs BufWritePost <buffer>
+    silent! autocmd! java_spotbugs Syntax <buffer>
+endfunction
+
 " Undo the stuff we changed.
-let b:undo_ftplugin = "setlocal suffixes< suffixesadd<" .
-               \     " formatoptions< comments< commentstring< path< includeexpr<" .
-               \     " | unlet! b:browsefilter"
+let b:undo_ftplugin = 'call JavaFileTypeCleanUp() | delfunction JavaFileTypeCleanUp'
 
 " See ":help vim9-mix".
 if !has("vim9script")
@@ -114,6 +231,19 @@ if exists("s:zip_func_upgradable")
     setlocal suffixesadd<
 endif
 
+if exists("*s:DispatchAction")
+    def! s:DispatchAction(path_action_pairs: list<list<any>>)
+       const name: string = expand('%:p')
+
+       for [path: string, Action: func: any] in path_action_pairs
+           if name =~# (path .. '.\{-}\.java\=$')
+               Action()
+               break
+           endif
+       endfor
+    enddef
+endif
+
 " Restore the saved compatibility options.
 let &cpo = s:save_cpo
 unlet s:save_cpo