diff --git a/README.md b/README.md index c303326..c06d15f 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,15 @@ CodeNarc image with reviewdog. ## Test local -``` -# build image +### build image + +```bash docker build -t docker.io/asaasdev/codenarc . +``` # run container + +```bash docker run --rm \ --workdir /testdata \ -e INPUT_REPORTER=local \ @@ -19,7 +23,7 @@ docker run --rm \ -e INPUT_FAIL_ON_ERROR=false \ -e INPUT_LEVEL=error \ -e INPUT_RULESETFILES=file:basic.xml \ + -e INPUT_RULESETS_CONTENT="$(cat testdata/basic.xml)" \ -v $(pwd)/testdata:/testdata \ docker.io/asaasdev/codenarc - ``` \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 2efe540..3de258f 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -8,28 +8,32 @@ VIOLATIONS_FLAG="/tmp/found_violations.txt" ALL_DIFF="/tmp/all_diff.txt" CHANGED_LINES_CACHE="/tmp/changed_lines.txt" CHANGED_FILES_CACHE="/tmp/changed_files.txt" +TMP_VIOLATIONS="/tmp/violations.tmp" cleanup_temp_files() { rm -f "$CODENARC_RESULT" "$LINE_VIOLATIONS" "$FILE_VIOLATIONS" "$VIOLATIONS_FLAG" \ "$ALL_DIFF" "$CHANGED_LINES_CACHE" "$CHANGED_FILES_CACHE" \ - "${FILE_VIOLATIONS}.formatted" >/dev/null 2>&1 + "${FILE_VIOLATIONS}.formatted" "$TMP_VIOLATIONS" >/dev/null 2>&1 } - trap 'cleanup_temp_files' EXIT run_codenarc() { report="${INPUT_REPORT:-compact:stdout}" includes_arg="" - [ -n "$INPUT_SOURCE_FILES" ] && includes_arg="-includes=${INPUT_SOURCE_FILES}" - + echo "🔍 Executando CodeNarc..." java -jar /lib/codenarc-all.jar \ -report="$report" \ -rulesetfiles="${INPUT_RULESETFILES}" \ -basedir="." \ - $includes_arg \ - > "$CODENARC_RESULT" + $includes_arg > "$CODENARC_RESULT" + + echo "" + echo "📋 Saída do CodeNarc:" + echo "" + cat "$CODENARC_RESULT" + echo "" } run_reviewdog_with_config() { @@ -39,7 +43,7 @@ run_reviewdog_with_config() { name="$4" filter_mode="$5" level="$6" - + < "$input_file" reviewdog \ -efm="$efm" \ -reporter="$reporter" \ @@ -51,58 +55,60 @@ run_reviewdog_with_config() { } separate_violations() { - grep -E ':[0-9]+:' "$CODENARC_RESULT" > "$LINE_VIOLATIONS" || true - grep -E ':null:|\|\|' "$CODENARC_RESULT" > "$FILE_VIOLATIONS" || true + grep -E '^[^:]+:[0-9]+:' "$CODENARC_RESULT" > "$LINE_VIOLATIONS" || true + grep -E '^[^:]+:(null:|\|\|)' "$CODENARC_RESULT" > "$FILE_VIOLATIONS" || true } run_reviewdog() { - echo "📤 Enviando resultados para reviewdog..." - + if ! grep -qE '^[^:]+:[0-9]+:|^[^:]+:(null:|\|\|)' "$CODENARC_RESULT"; then + return 0 + fi + separate_violations + echo "📤 Enviando resultados para reviewdog..." + echo "" + if [ -s "$LINE_VIOLATIONS" ]; then - echo "📤 Enviando violações line-based (${INPUT_REPORTER:-github-pr-check})..." run_reviewdog_with_config "$LINE_VIOLATIONS" "%f:%l:%m" \ "${INPUT_REPORTER:-github-pr-check}" "codenarc" \ "${INPUT_FILTER_MODE}" "${INPUT_LEVEL}" fi - + if [ -s "$FILE_VIOLATIONS" ]; then - true > "${FILE_VIOLATIONS}.formatted" while read -r violation; do if echo "$violation" | grep -q '||'; then - echo "$violation" | sed 's/||/::/' + echo "$violation" | sed 's/||/::/g' else - echo "$violation" | sed 's/:null:/::/' + echo "$violation" | sed 's/:null:/::/g' fi done < "$FILE_VIOLATIONS" > "${FILE_VIOLATIONS}.formatted" - + if [ "${INPUT_REPORTER}" = "local" ]; then - echo "📤 Enviando violações file-based (local)..." run_reviewdog_with_config "${FILE_VIOLATIONS}.formatted" "%f::%m" \ "local" "codenarc" "nofilter" "${INPUT_LEVEL}" else - echo "📤 Enviando violações file-based (github-pr-check)..." run_reviewdog_with_config "${FILE_VIOLATIONS}.formatted" "%f::%m" \ "github-pr-check" "codenarc" "nofilter" "warning" fi fi - - # fallback se nao houver violacoes categorizadas - if [ ! -s "$LINE_VIOLATIONS" ] && [ ! -s "$FILE_VIOLATIONS" ]; then - echo "📝 Executando reviewdog padrão..." - run_reviewdog_with_config "$CODENARC_RESULT" "%f:%l:%m" \ - "${INPUT_REPORTER:-github-pr-check}" "codenarc" \ - "${INPUT_FILTER_MODE}" "${INPUT_LEVEL}" - fi } generate_git_diff() { + if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "⚠️ Diretório não é um repositório Git; nenhuma comparação de diff será feita." + return 0 + fi + if [ -n "$GITHUB_BASE_SHA" ] && [ -n "$GITHUB_HEAD_SHA" ]; then git fetch origin "$GITHUB_BASE_SHA" --depth=1 2>/dev/null || true git fetch origin "$GITHUB_HEAD_SHA" --depth=1 2>/dev/null || true git diff -U0 "$GITHUB_BASE_SHA" "$GITHUB_HEAD_SHA" -- '*.groovy' else + if ! git rev-parse HEAD~1 >/dev/null 2>&1; then + echo "⚠️ Nenhum commit anterior para comparar; diff vazio." + return 0 + fi git diff -U0 HEAD~1 -- '*.groovy' fi } @@ -119,10 +125,13 @@ parse_diff_range() { build_changed_lines_cache() { true > "$CHANGED_LINES_CACHE" true > "$CHANGED_FILES_CACHE" - + generate_git_diff > "$ALL_DIFF" 2>/dev/null || true - [ ! -s "$ALL_DIFF" ] && return 0 - + [ ! -s "$ALL_DIFF" ] && { + echo "ℹ️ Nenhum diff detectado; prosseguindo com cache vazio." + return 0 + } + current_file="" while read -r line; do case "$line" in @@ -136,10 +145,10 @@ build_changed_lines_cache() { range_info=$(parse_diff_range "$range") start=$(echo "$range_info" | cut -d' ' -f1) count=$(echo "$range_info" | cut -d' ' -f2) - + case "$start" in ''|*[!0-9]*) continue ;; esac case "$count" in ''|*[!0-9]*) continue ;; esac - + i="$start" while [ "$i" -lt "$((start + count))" ]; do echo "$current_file:$i" >> "$CHANGED_LINES_CACHE" @@ -150,6 +159,25 @@ build_changed_lines_cache() { done < "$ALL_DIFF" } +get_rule_priority() { + rule_name="$1" + priority=$(echo "$INPUT_RULESETS_CONTENT" | grep -B 2 "name='$rule_name'" | grep -o 'priority" value="[0-9]' | head -1 | cut -d'"' -f3) + if [ -z "$priority" ]; then + priority=$(echo "$INPUT_RULESETS_CONTENT" | grep "class='[^']*${rule_name}Rule'" -A 5 | grep -o 'priority" value="[0-9]' | head -1 | cut -d'"' -f3) + fi + if [ -z "$priority" ]; then + priority=$(echo "$INPUT_RULESETS_CONTENT" | grep "class='[^']*${rule_name}'" -A 5 | grep -o 'priority" value="[0-9]' | head -1 | cut -d'"' -f3) + fi + if [ -z "$priority" ]; then + priority=$(echo "$INPUT_RULESETS_CONTENT" | grep -A 3 "path='[^']*${rule_name}" | grep -o 'priority" value="[0-9]' | head -1 | cut -d'"' -f3) + fi + echo "${priority:-2}" +} + +extract_rule_name() { + echo "$1" | sed -E 's/^[^:]+:[^:]+:([A-Za-z0-9]+).*/\1/' +} + get_p1_count() { p1_count=$(grep -Eo "p1=[0-9]+" "$CODENARC_RESULT" | cut -d'=' -f2 | head -1) echo "${p1_count:-0}" @@ -162,9 +190,7 @@ get_allowed_patterns() { file_matches_patterns() { file="$1" patterns="$2" - [ -z "$patterns" ] && return 0 - for pattern in $patterns; do echo "$file" | grep -Eq "$pattern" && return 0 done @@ -181,48 +207,61 @@ is_file_changed() { check_blocking_rules() { echo "🔎 Verificando violações bloqueantes (priority 1)..." - [ ! -f "$CODENARC_RESULT" ] && echo "❌ Resultado não encontrado" && return 1 - + p1_count=$(get_p1_count) - echo "📊 Total de P1 encontradas: $p1_count" - - [ "$p1_count" -eq 0 ] && echo "✅ Nenhuma P1 detectada → merge permitido" && return 0 - - echo "⚠️ Verificando P1s em linhas alteradas..." + if [ "$p1_count" -eq 0 ]; then + echo "✅ Nenhuma violação P1 detectada" + echo "" + return 0 + fi + + echo "📊 Violações P1 nos arquivos analisados: ${p1_count:-0}" + echo "⚙️ Analisando diff para identificar P1 em linhas/arquivos alterados..." build_changed_lines_cache - allowed_patterns=$(get_allowed_patterns) - [ -n "$allowed_patterns" ] && echo "🧩 Analisando apenas arquivos filtrados por INPUT_SOURCE_FILES" - + [ -n "$allowed_patterns" ] && echo "🧩 Aplicando filtro de arquivos: INPUT_SOURCE_FILES" + echo "0" > "$VIOLATIONS_FLAG" - - grep -E ':[0-9]+:|:null:|\|\|' "$CODENARC_RESULT" | while IFS=: read -r file line rest; do + p1_in_diff=0 + grep -E '^[^:]+:[0-9]+:|^[^:]+:(null:|\|\|)' "$CODENARC_RESULT" > "$TMP_VIOLATIONS" || true + + while IFS=: read -r file line rest; do + [ -z "$file" ] && continue if echo "$file" | grep -q '||'; then file=$(echo "$file" | cut -d'|' -f1) line="" fi - [ -z "$file" ] && continue file_matches_patterns "$file" "$allowed_patterns" || continue - + rule_name=$(extract_rule_name "$file:$line:$rest") + priority=$(get_rule_priority "$rule_name") + [ "$priority" != "1" ] && continue + if [ -z "$line" ] || [ "$line" = "null" ]; then if is_file_changed "$file"; then - echo "📍 Violação file-based em arquivo alterado: $file" - echo "1" > "$VIOLATIONS_FLAG" && break + p1_in_diff=$((p1_in_diff + 1)) + echo " ⛔ P1 #$p1_in_diff: $rule_name (file-based) em $file" + echo "1" > "$VIOLATIONS_FLAG" fi elif is_line_changed "$line" "$file"; then - echo "📍 Violação em linha alterada: $file:$line" - echo "1" > "$VIOLATIONS_FLAG" && break + p1_in_diff=$((p1_in_diff + 1)) + echo " ⛔ P1 #$p1_in_diff: $rule_name na linha $line de $file" + echo "1" > "$VIOLATIONS_FLAG" fi - done - + done < "$TMP_VIOLATIONS" + + rm -f "$TMP_VIOLATIONS" + echo "" if [ "$(cat "$VIOLATIONS_FLAG")" -eq 1 ]; then - echo "⛔ P1s existem E há violações em linhas alteradas" - echo "💡 Corrija as violacoes ou use o bypass autorizado pelo coordenador." + echo "❌ BLOQUEIO: $p1_in_diff violação(ões) P1 encontrada(s) em linhas/arquivos alterados do PR" + echo "💡 Corrija as violações acima ou utilize o bypass autorizado" exit 1 else - echo "✅ P1s existem mas fora das linhas alteradas → merge permitido" + p1_outside_diff=$((p1_count - p1_in_diff)) + echo "✅ APROVADO: Nenhuma violação P1 em linhas/arquivos alterados do PR" + [ "$p1_outside_diff" -gt 0 ] && echo "ℹ️ ${p1_outside_diff} violação(ões) P1 em código não modificado (não bloqueia)" fi + echo "" } if [ -n "${GITHUB_WORKSPACE}" ]; then @@ -236,4 +275,4 @@ run_codenarc run_reviewdog check_blocking_rules -echo "🏁 Concluído com sucesso" \ No newline at end of file +echo "🏁 Concluído com sucesso" diff --git a/testdata/basic.xml b/testdata/basic.xml index 54df9b7..86df763 100644 --- a/testdata/basic.xml +++ b/testdata/basic.xml @@ -1,52 +1,213 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd" + xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testdata/subdir/text.md b/testdata/subdir/text.md deleted file mode 100644 index 5b33346..0000000 --- a/testdata/subdir/text.md +++ /dev/null @@ -1,2 +0,0 @@ -Determinisitic result is important! - diff --git a/testdata/test.groovy b/testdata/test.groovy index 0897f5c..6feb46a 100644 --- a/testdata/test.groovy +++ b/testdata/test.groovy @@ -1,13 +1,41 @@ package test +import org.springframework.web.util.UriComponentsBuilder + class Test { + // P1 - ForceHttps + String url = "http://example.com" + + // P1 - VerifyUriComponentsBuilderVulnerability + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("test") + boolean before() { - return true + return true // P2 - ImplicitReturnStatement } - boolean after() { true } + boolean after() { true } // P2 - ImplicitReturnStatement - void after() { + void after() { // P2 - EmptyMethod + } + + // P2 - Multiple violations + def x = new ArrayList() // P2 - ExplicitArrayListInstantiation + String msg = 'Hello ${name}' // P2 - GStringExpressionWithinString + + void testMethod() { + if (true) { // P2 - ConstantIfExpression + println "test" // P2 - PrintlnRule + } + + // P2 - AssignmentInConditional + if (x = 5) { + return + } + + // P2 - ComparisonWithSelf + if (x == x) { + return + } } -} +} \ No newline at end of file diff --git a/testdata/text.md b/testdata/text.md deleted file mode 100644 index 5025db8..0000000 --- a/testdata/text.md +++ /dev/null @@ -1,5 +0,0 @@ -Determinisitic result is important. - -colour # <= Check -locale - -langauge