diff --git a/scripts/clippy_report_generator.sh b/scripts/clippy_report_generator.sh new file mode 100755 index 000000000..68a9ed47f --- /dev/null +++ b/scripts/clippy_report_generator.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash + +# scripts/clippy_report_html.sh +# +# ----------------------------------------------------------------------------- +# Clippy → HTML Report Generator +# +# USAGE (from project root): +# ./scripts/clippy_report_generator.sh [--manifest-path ] [--out ] +# +# DEFAULTS (resolved relative to the script’s directory): +# --manifest-path $SCRIPT_DIR/../src/wasmtime/Cargo.toml +# --out $SCRIPT_DIR/../clippy_report.html +# +# EXAMPLES: +# # 1) Standard run from repo root, uses defaults: +# ./scripts/clippy_report_generator.sh +# +# # 2) Point at a different Cargo.toml: +# ./scripts/clippy_report_generator.sh --manifest-path ./crates/foo/Cargo.toml +# +# # 3) Write the report someplace else: +# ./scripts/clippy_report_generator.sh --out ./target/clippy_report.html +# +# REQUIREMENTS: +# - cargo + clippy component installed +# - jq, sed, grep (POSIX-ish environment) +# +# EXIT CODE: +# Mirrors clippy’s status (0 on success, non-zero on clippy failure). +# ----------------------------------------------------------------------------- + + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MANIFEST_PATH="$SCRIPT_DIR/../src/wasmtime/Cargo.toml" +echo "Manifest path: $MANIFEST_PATH" +OUT_HTML="$SCRIPT_DIR/../clippy_report.html" +echo "Output HTML: $OUT_HTML" + +while [[ $# -gt 0 ]]; do + case "$1" in + --manifest-path) MANIFEST_PATH="${2:-}"; shift 2 ;; + --out) OUT_HTML="${2:-}"; shift 2 ;; + *) echo "Unknown arg: $1" >&2; exit 2 ;; + esac +done + +RAW_JSON="$(mktemp)" +JQ_FILTER="$(mktemp)" +trap 'rm -f "$RAW_JSON" "$JQ_FILTER"' EXIT + +export CARGO_TERM_COLOR=never + +if ! cargo clippy \ + --manifest-path "$MANIFEST_PATH" \ + --all-features \ + --keep-going \ + --message-format=json \ + -- \ + -A clippy::not_unsafe_ptr_arg_deref \ + -A clippy::absurd_extreme_comparisons \ + >"$RAW_JSON" 2>&1 +then + CLIPPY_STATUS=$? +else + CLIPPY_STATUS=0 +fi + +cat >"$JQ_FILTER" <<'JQ' +def h: + tostring + | gsub("&"; "&") + | gsub("<"; "<") + | gsub(">"; ">") + | gsub("\""; """) + | gsub("'"; "'"); + +# Arg-less filter version; works with `... | nl_to_br` +def nl_to_br: + gsub("\r?\n"; "
") + | gsub("\\\\r?\\\\n"; "
"); + +def diag_to_html: + ( + "
  • " + + "
    " + + "" + (.code|h) + " — " + (.message|h) + + ( if .span != null then + " (" + + (.span.file_name|h) + ":" + + (.span.line_start|tostring) + ":" + + (.span.column_start|tostring) + "–" + + (.span.line_end|tostring) + ":" + + (.span.column_end|tostring) + ")" + else "" end ) + + "" + + ( if (.rendered // "") != "" then + "
    " + ( (.rendered|h) | nl_to_br ) + "
    " + else "" end ) + + "
  • " + ); + + +[ inputs + | fromjson? + | select(.reason == "compiler-message") + | select(.message.level == "warning" or .message.level == "error") + | { + crate: .target.name, + file: ( + (.message.spans[]? | select(.is_primary == true) | .file_name) + // (.message.spans[0]?.file_name // "unknown") + ), + code: (.message.code?.code // "unknown"), + level: .message.level, + message: .message.message, + rendered: (.message.rendered // ""), + span: ( + (.message.spans[]? | select(.is_primary == true)) + // .message.spans[0] + ) + } +] as $diags +| ($diags | length) as $total +| ($diags | map(select(.level=="error")) | length) as $errors +| ($diags | map(select(.level=="warning")) | length) as $warnings +| ($diags | sort_by(.crate, .file, .level, .code, .message) + | group_by(.crate)) as $by_crate +| "" + + "" + + "Clippy Report" + + "" + + "

    Clippy Report

    " + + "

    Total: " + ($total|tostring) + + " — Errors: " + ($errors|tostring) + + " — Warnings: " + ($warnings|tostring) + "

    " + + ( + $by_crate + | map( + . as $c + | ($c[0].crate) as $crate + | ($c | group_by(.file)) as $files + | "

    " + ($crate|h) + + " (" + (($c|length)|tostring) + ")

    " + + ( + $files + | map( + . as $f + | ($f[0].file) as $file + | ($f | map({code, level, message, rendered, span})) as $ds + | ($ds|length) as $cnt + | ($ds | map(select(.level=="error")) | length) as $errs + | ($ds | map(select(.level=="warning")) | length) as $warns + | "
    " + + ($file|h) + " — " + ($cnt|tostring) + + " issues (" + ($errs|tostring) + " errors, " + + ($warns|tostring) + " warnings)" + + "
      " + + ( $ds | map( diag_to_html ) | join("") ) + + "
    " + ) + | join("") + ) + + "
    " + ) + | join("") + ) + + "" +JQ + +sed -r 's/\x1b\[[0-9;]*m//g' "$RAW_JSON" \ + | grep -E '^[[:space:]]*\{' \ + | jq -Rnc -f "$JQ_FILTER" > "$OUT_HTML" + +echo "Wrote: $OUT_HTML" +exit "${CLIPPY_STATUS-0}"