ale icon indicating copy to clipboard operation
ale copied to clipboard

Add support for codenarc

Open rmartine-ias opened this issue 1 year ago • 0 comments

Name: Codenarc URL: https://codenarc.org/

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
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',
\})

rmartine-ias avatar Dec 06 '23 21:12 rmartine-ias