CodeNarc analyzes Groovy code for defects, bad practices, inconsistencies, style issues and more. A flexible framework for rules, rulesets and custom rules means it’s easy to configure CodeNarc to fit into your project. Build tool, framework support, and report generation are all enterprise ready.
We use this at my work; I have a hacked together patch to ALE which I have not had the time to clean up. It is docker-only. I'm adding it below in case it's a useful starting point.
ale_linters/groovy/codenarc.vim
```vim
call ale#Set('groovy_codenarc_use_docker', 'always')
" TODO test with official image
" TODO PR new image
call ale#Set('groovy_codenarc_docker_image', 'codenarc/codenarc')
call ale#Set('groovy_codenarc_options', '')
" call ale#Set('groovy_codenarc_classpath', '')
" Look at kotlinc.vim for inspiration
" Here's a partial
" #!/usr/bin/env bash
" export CODENARC_JAR="$HOME/bin/CodeNarc-3.1.0.jar"
" export SLF4J_JAR="/Users/rmartine/.m2/repository/org/slf4j/slf4j-api/1.7.35/slf4j-api-1.7.35.jar"
" export SLF4J_SIMPLE="/Users/rmartine/.m2/repository/org/slf4j/slf4j-simple/1.7.35/slf4j-simple-1.7.35.jar"
" export GROOVY_HOME="/Users/rmartine/.sdkman/candidates/groovy/current/"
" export GROOVY_JAR="$GROOVY_HOME/lib/*:$GROOVY_HOME/lib/extras-jaxb/*"
" java \
" -classpath "$GROOVY_JAR:$CODENARC_JAR:$SLF4J_JAR:$SLF4J_SIMPLE" \
" org.codenarc.CodeNarc
function! ale_linters#groovy#codenarc#GetExecutable(buffer) abort
let l:use_docker = ale#Var(a:buffer, 'groovy_codenarc_use_docker')
" check for mandatory directives
if l:use_docker is# 'never'
"TODO Implement running local like
return 'UNIMPLEMENTED'
elseif l:use_docker is# 'always'
return 'docker'
endif
" if we reach here, we want to use 'hadolint' if present...
" if executable('hadolint')
" return 'hadolint'
" endif
"... and 'docker' as a fallback.
return 'docker'
endfunction
function! ale_linters#groovy#codenarc#GetCommand(buffer) abort
let l:command = ale_linters#groovy#codenarc#GetExecutable(a:buffer)
if l:command is# 'docker'
let l:path = fnamemodify(bufname(a:buffer), ':p')
let l:file = fnamemodify(l:path, ':t')
let l:tmpdir = fnamemodify(tempname(), ':h:h')
return 'docker run --rm'
\ . ' -v %t:h:"/ws/":ro'
\ . ale#Pad(ale#Var(a:buffer, 'groovy_codenarc_docker_image'))
\ . ale#Pad(ale#Var(a:buffer, 'groovy_codenarc_options'))
\ . ' -includes=./%t:t'
\ . ' -rulesetfiles="rulesets/groovyism.xml,rulesets/basic.xml,rulesets/braces.xml,rulesets/imports.xml"'
\ . ' -report=json:stdout'
endif
return 'UNIMPLEMENTED'
endfunction
function! ale_linters#groovy#codenarc#Handle(buffer, lines) abort
let l:output = []
let l:json = {}
for l:line in a:lines
if l:line[0:10] isnot# '{"codeNarc"'
continue
endif
let l:json = json_decode(l:line)
break
endfor
if empty(l:json)
echoerr join(a:lines, '\n')
return
endif
if !has_key(l:json, 'summary')
\|| l:json['summary']['filesWithViolations'] == 0
return []
endif
let l:file = l:json['packages'][0]['files'][0]
" TODO error handling
" TODO standardize on [''] or . syntax
if l:file.name isnot# fnamemodify(bufname(a:buffer), ':p:t')
return []
endif
for l:violation in l:file['violations']
let l:text = l:violation.ruleName
if has_key(l:violation, 'message')
let l:text = l:violation.message
endif
let l:lint = {
\ 'lnum': l:violation.lineNumber,
\ 'code': l:violation.ruleName,
\ 'text': l:text,
\ 'type': l:violation.priority == 1 ? 'E': 'W',
\}
let l:rules = filter(l:json["rules"], 'v:val.name is# l:violation.ruleName')
if len(l:rules) == 1
let l:lint.detail = l:rules[0].name . ":\n" . l:rules[0].description
let l:lint.text = l:lint.text . ': ' . l:rules[0].description
elseif len(l:rules) > 1
echoerr 'Multiple rules with name ' . l:violation.ruleName
echoerr join(l:json["rules"], ";")
endif
" Sometimes you get zero, like for GstringExpressionWithinString
call add(l:output, l:lint)
endfor
return l:output
endfunction
call ale#linter#Define('groovy', {
\ 'name': 'CodeNarc',
\ 'executable': function('ale_linters#groovy#codenarc#GetExecutable'),
\ 'command': function('ale_linters#groovy#codenarc#GetCommand'),
\ 'callback': 'ale_linters#groovy#codenarc#Handle',
\ 'output_stream': 'both',
\})
```
Name: Codenarc URL: https://codenarc.org/
We use this at my work; I have a hacked together patch to ALE which I have not had the time to clean up. It is docker-only. I'm adding it below in case it's a useful starting point.
```vim call ale#Set('groovy_codenarc_use_docker', 'always') " TODO test with official image " TODO PR new image call ale#Set('groovy_codenarc_docker_image', 'codenarc/codenarc') call ale#Set('groovy_codenarc_options', '') " call ale#Set('groovy_codenarc_classpath', '') " Look at kotlinc.vim for inspiration " Here's a partial " #!/usr/bin/env bash " export CODENARC_JAR="$HOME/bin/CodeNarc-3.1.0.jar" " export SLF4J_JAR="/Users/rmartine/.m2/repository/org/slf4j/slf4j-api/1.7.35/slf4j-api-1.7.35.jar" " export SLF4J_SIMPLE="/Users/rmartine/.m2/repository/org/slf4j/slf4j-simple/1.7.35/slf4j-simple-1.7.35.jar" " export GROOVY_HOME="/Users/rmartine/.sdkman/candidates/groovy/current/" " export GROOVY_JAR="$GROOVY_HOME/lib/*:$GROOVY_HOME/lib/extras-jaxb/*" " java \ " -classpath "$GROOVY_JAR:$CODENARC_JAR:$SLF4J_JAR:$SLF4J_SIMPLE" \ " org.codenarc.CodeNarc function! ale_linters#groovy#codenarc#GetExecutable(buffer) abort let l:use_docker = ale#Var(a:buffer, 'groovy_codenarc_use_docker') " check for mandatory directives if l:use_docker is# 'never' "TODO Implement running local like return 'UNIMPLEMENTED' elseif l:use_docker is# 'always' return 'docker' endif " if we reach here, we want to use 'hadolint' if present... " if executable('hadolint') " return 'hadolint' " endif "... and 'docker' as a fallback. return 'docker' endfunction function! ale_linters#groovy#codenarc#GetCommand(buffer) abort let l:command = ale_linters#groovy#codenarc#GetExecutable(a:buffer) if l:command is# 'docker' let l:path = fnamemodify(bufname(a:buffer), ':p') let l:file = fnamemodify(l:path, ':t') let l:tmpdir = fnamemodify(tempname(), ':h:h') return 'docker run --rm' \ . ' -v %t:h:"/ws/":ro' \ . ale#Pad(ale#Var(a:buffer, 'groovy_codenarc_docker_image')) \ . ale#Pad(ale#Var(a:buffer, 'groovy_codenarc_options')) \ . ' -includes=./%t:t' \ . ' -rulesetfiles="rulesets/groovyism.xml,rulesets/basic.xml,rulesets/braces.xml,rulesets/imports.xml"' \ . ' -report=json:stdout' endif return 'UNIMPLEMENTED' endfunction function! ale_linters#groovy#codenarc#Handle(buffer, lines) abort let l:output = [] let l:json = {} for l:line in a:lines if l:line[0:10] isnot# '{"codeNarc"' continue endif let l:json = json_decode(l:line) break endfor if empty(l:json) echoerr join(a:lines, '\n') return endif if !has_key(l:json, 'summary') \|| l:json['summary']['filesWithViolations'] == 0 return [] endif let l:file = l:json['packages'][0]['files'][0] " TODO error handling " TODO standardize on [''] or . syntax if l:file.name isnot# fnamemodify(bufname(a:buffer), ':p:t') return [] endif for l:violation in l:file['violations'] let l:text = l:violation.ruleName if has_key(l:violation, 'message') let l:text = l:violation.message endif let l:lint = { \ 'lnum': l:violation.lineNumber, \ 'code': l:violation.ruleName, \ 'text': l:text, \ 'type': l:violation.priority == 1 ? 'E': 'W', \} let l:rules = filter(l:json["rules"], 'v:val.name is# l:violation.ruleName') if len(l:rules) == 1 let l:lint.detail = l:rules[0].name . ":\n" . l:rules[0].description let l:lint.text = l:lint.text . ': ' . l:rules[0].description elseif len(l:rules) > 1 echoerr 'Multiple rules with name ' . l:violation.ruleName echoerr join(l:json["rules"], ";") endif " Sometimes you get zero, like for GstringExpressionWithinString call add(l:output, l:lint) endfor return l:output endfunction call ale#linter#Define('groovy', { \ 'name': 'CodeNarc', \ 'executable': function('ale_linters#groovy#codenarc#GetExecutable'), \ 'command': function('ale_linters#groovy#codenarc#GetCommand'), \ 'callback': 'ale_linters#groovy#codenarc#Handle', \ 'output_stream': 'both', \}) ```ale_linters/groovy/codenarc.vim