aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-11-29 08:54:47 +0800
committerzeertzjq <zeertzjq@outlook.com>2024-11-29 08:57:00 +0800
commit8d7d225caa12e5a25f6853a54a2fd6d144342d3c (patch)
treef456f05a86d76bcb97f3b276d743fab227ef8def
parent146b8300a145efa64e579527da8606546a36162b (diff)
downloadrneovim-8d7d225caa12e5a25f6853a54a2fd6d144342d3c.tar.gz
rneovim-8d7d225caa12e5a25f6853a54a2fd6d144342d3c.tar.bz2
rneovim-8d7d225caa12e5a25f6853a54a2fd6d144342d3c.zip
vim-patch:65311c6: runtime(compiler): include spotbugs Java linter
closes: vim/vim#16001 https://github.com/vim/vim/commit/65311c6f472de67b368d83441ca5e93da86161f4 Co-authored-by: Konfekt <Konfekt@users.noreply.github.com> Co-authored-by: Aliaksei Budavei <0x000c70@gmail.com>
-rw-r--r--runtime/autoload/spotbugs.vim250
-rw-r--r--runtime/compiler/javac.vim8
-rw-r--r--runtime/compiler/maven.vim2
-rw-r--r--runtime/compiler/spotbugs.vim189
-rw-r--r--runtime/doc/quickfix.txt112
-rw-r--r--runtime/ftplugin/java.vim138
6 files changed, 686 insertions, 13 deletions
diff --git a/runtime/autoload/spotbugs.vim b/runtime/autoload/spotbugs.vim
new file mode 100644
index 0000000000..9161395794
--- /dev/null
+++ b/runtime/autoload/spotbugs.vim
@@ -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:
diff --git a/runtime/compiler/javac.vim b/runtime/compiler/javac.vim
index 9bd4cdf270..53cd772ed8 100644
--- a/runtime/compiler/javac.vim
+++ b/runtime/compiler/javac.vim
@@ -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,
diff --git a/runtime/compiler/maven.vim b/runtime/compiler/maven.vim
index ef8d8a6fb2..72e74e301d 100644
--- a/runtime/compiler/maven.vim
+++ b/runtime/compiler/maven.vim
@@ -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
index 0000000000..72a5084976
--- /dev/null
+++ b/runtime/compiler/spotbugs.vim
@@ -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:
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index b3399b2766..4811a51a87 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1317,9 +1317,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,
diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim
index 55b358374f..6e12fe2fe5 100644
--- a/runtime/ftplugin/java.vim
+++ b/runtime/ftplugin/java.vim
@@ -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