- 合併、去重、排序原本寫在兩支技能的步驟文字裡,各寫一份就會各自演化。改由腳本執行,並定下六欄 TSV 的回報格式。格式不對就擋下並指出是第幾列,不猜、不放行、不自行補欄。 - 另一支腳本列出本次改到的註解行,清理範圍不再靠肉眼判讀。 - 註解樣式與非程式碼副檔名清單沿用註解範圍腳本的同一份,不另立第二套判準。markdown 的標題行開頭就是井字號,判準不一致就會把整份文件當成註解。
111 lines
4.0 KiB
Bash
Executable File
111 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# merge-findings.sh — 合併多組審查發現,依位置去重,再依嚴重度排序。
|
||
# 用法:merge-findings.sh [發現檔…]
|
||
# 給檔名就讀那幾個檔,不給檔名就讀 stdin。code-review 六組與 api-doc 兩個面向
|
||
# 共用這一支,兩支技能都不再自己描述合併與排序規則。
|
||
#
|
||
# 輸入格式(TSV,一列一筆發現,六欄,欄位之間一個 tab):
|
||
# 1 file 檔案路徑,相對專案根目錄,不得留空
|
||
# 2 line 行號,正整數
|
||
# 3 severity 嚴重度,只收 高、中、低
|
||
# 4 group 組別或面向代號,例如 2 或 1 狀態碼
|
||
# 5 item 發現名稱,壞味道名稱或稽核項目名稱
|
||
# 6 detail 一句證據加上建議修法
|
||
# 空白列、開頭是 # 的列、第一欄為「無發現」的列一律略過。
|
||
#
|
||
# 輸出(TSV,同樣六欄,一個位置一列,印到 stdout):
|
||
# file 與 line 相同的發現合併成一列。
|
||
# severity 取該位置最高的一級。group、item、detail 依輸入順序以「|」相連。
|
||
# 排序:嚴重度 高 → 中 → 低;同一級再依 file 字典序、line 數值遞增。
|
||
#
|
||
# 結束碼:
|
||
# 0 合併成功,至少印出一列
|
||
# 1 沒有任何發現(輸入沒有可用的資料列),呼叫端回報「無發現」
|
||
# 2 參數或環境錯誤(找不到輸入檔,或缺 awk、sort、cut)
|
||
# 3 輸入格式錯誤(欄數不是 6、行號不是正整數,或嚴重度不在 高、中、低)
|
||
#
|
||
# 護欄:
|
||
# 格式錯誤一律 exit 3 並在 stderr 指出是第幾個檔的第幾列,不猜、不放行、不自行補欄。
|
||
# 錯誤訊息一律印繁中到 stderr,正常輸出只走 stdout。
|
||
set -u
|
||
|
||
for cmd in awk sort cut; do
|
||
command -v "$cmd" >/dev/null 2>&1 || {
|
||
echo "錯誤:環境缺 $cmd,無法合併發現。" >&2
|
||
exit 2
|
||
}
|
||
done
|
||
|
||
for f in "$@"; do
|
||
case "$f" in
|
||
-*) echo "用法:merge-findings.sh [發現檔…](不給檔名時讀 stdin)" >&2; exit 2 ;;
|
||
esac
|
||
[ -f "$f" ] || { echo "錯誤:找不到輸入檔 $f。請確認路徑後重試。" >&2; exit 2; }
|
||
done
|
||
|
||
TAB=$(printf '\t')
|
||
TMP="${TMPDIR:-/tmp}/jsc-merge-findings.$$"
|
||
trap 'rm -f "$TMP" "$TMP.sorted"' EXIT INT TERM
|
||
|
||
# 第一欄先放排序用的嚴重度序位,排完再切掉。
|
||
awk -F"$TAB" '
|
||
BEGIN { OFS = "\t"; n = 0; bad = 0 }
|
||
{
|
||
sub(/\r$/, "", $0)
|
||
if ($0 ~ /^[ \t]*$/) next
|
||
if ($0 ~ /^#/) next
|
||
if ($1 == "無發現") next
|
||
where = (FILENAME == "" || FILENAME == "-") ? "stdin" : FILENAME
|
||
if (NF != 6) {
|
||
printf("錯誤:%s 第 %d 列有 %d 欄,應為 6 欄。\n", where, FNR, NF) > "/dev/stderr"
|
||
bad = 1; next
|
||
}
|
||
if ($1 == "") {
|
||
printf("錯誤:%s 第 %d 列的檔案路徑是空的。\n", where, FNR) > "/dev/stderr"
|
||
bad = 1; next
|
||
}
|
||
if ($2 !~ /^[0-9]+$/ || $2 + 0 < 1) {
|
||
printf("錯誤:%s 第 %d 列的行號「%s」不是正整數。\n", where, FNR, $2) > "/dev/stderr"
|
||
bad = 1; next
|
||
}
|
||
if ($3 != "高" && $3 != "中" && $3 != "低") {
|
||
printf("錯誤:%s 第 %d 列的嚴重度「%s」不在 高、中、低。\n", where, FNR, $3) > "/dev/stderr"
|
||
bad = 1; next
|
||
}
|
||
rank = ($3 == "高") ? 1 : (($3 == "中") ? 2 : 3)
|
||
key = $1 SUBSEP $2
|
||
if (!(key in seen)) {
|
||
seen[key] = 1
|
||
order[++n] = key
|
||
kfile[key] = $1; kline[key] = $2; krank[key] = rank
|
||
kgroup[key] = $4; kitem[key] = $5; kdetail[key] = $6
|
||
} else {
|
||
if (rank < krank[key]) krank[key] = rank
|
||
kgroup[key] = kgroup[key] "|" $4
|
||
kitem[key] = kitem[key] "|" $5
|
||
kdetail[key] = kdetail[key] "|" $6
|
||
}
|
||
}
|
||
END {
|
||
if (bad) exit 3
|
||
if (n == 0) exit 1
|
||
for (i = 1; i <= n; i++) {
|
||
k = order[i]
|
||
sev = (krank[k] == 1) ? "高" : ((krank[k] == 2) ? "中" : "低")
|
||
print krank[k], kfile[k], kline[k], sev, kgroup[k], kitem[k], kdetail[k]
|
||
}
|
||
}
|
||
' "$@" > "$TMP"
|
||
rc=$?
|
||
[ "$rc" -eq 0 ] || exit "$rc"
|
||
|
||
LC_ALL=C sort -t"$TAB" -k1,1n -k2,2 -k3,3n "$TMP" > "$TMP.sorted" || {
|
||
echo "錯誤:排序失敗。" >&2
|
||
exit 2
|
||
}
|
||
cut -f2- "$TMP.sorted" || {
|
||
echo "錯誤:輸出失敗。" >&2
|
||
exit 2
|
||
}
|
||
exit 0
|