1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/bin/bash
set -uo pipefail
# Flags
FAIL_INVALID_FMT=false
VERBOSE=false
# Github API access token (Optional - necessary for private repositories.)
GH_API_TOKEN="${CI_TODO_CHECKER_PAT:-""}"
AUTH=""
if [[ $GH_API_TOKEN != "" ]]; then
AUTH="Authorization: token $GH_API_TOKEN"
fi
# Default org and repo
ORG="ethereum-optimism"
REPO="optimism"
# Counter for issues that were not found and issues that are still open.
NOT_FOUND_COUNT=0
MISMATCH_COUNT=0
OPEN_COUNT=0
declare -a OPEN_ISSUES
# Colors
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
GREY='\033[1;30m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# Parse flags
#
# `--strict`: Toggle strict mode; Will fail if any TODOs are found that don't match the expected
# `--verbose`: Toggle verbose mode; Will print out details about each TODO
for arg in "$@"; do
case $arg in
--strict)
FAIL_INVALID_FMT=true
shift
;;
--verbose)
VERBOSE=true
shift
;;
esac
done
# Use ripgrep to search for the pattern in all files within the repo
todos=$(rg -o --with-filename -n -g '!ops/scripts/todo-checker.sh' 'TODO\(([^)]+)\): [^,;]*')
# Check each TODO comment in the repo
IFS=$'\n' # Set Internal Field Separator to newline for iteration
for todo in $todos; do
# Extract the text inside the parenthesis
FILE=$(echo $todo | awk -F':' '{print $1}')
LINE_NUM=$(echo $todo | awk -F':' '{print $2}')
ISSUE_REFERENCE=$(echo $todo | sed -n 's/.*TODO(\([^)]*\)).*/\1/p')
# Parse the format of the TODO comment. There are 3 supported formats:
# * TODO(<issue_number>): <description> (Default org & repo: "ethereum-optimism/monorepo")
# * TODO(repo#<issue_number>): <description> (Default org "ethereum-optimism")
# * TODO(org/repo#<issue_number>): <description>
#
# Check if it's just a number
if [[ $ISSUE_REFERENCE =~ ^[0-9]+$ ]]; then
REPO_FULL="$ORG/$REPO"
ISSUE_NUM="$ISSUE_REFERENCE"
# Check for org_name/repo_name#number format
elif [[ $ISSUE_REFERENCE =~ ^([^/]+)/([^#]+)#([0-9]+)$ ]]; then
REPO_FULL="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
ISSUE_NUM="${BASH_REMATCH[3]}"
# Check for repo_name#number format
elif [[ $ISSUE_REFERENCE =~ ^([^#]+)#([0-9]+)$ ]]; then
REPO_FULL="$ORG/${BASH_REMATCH[1]}"
ISSUE_NUM="${BASH_REMATCH[2]}"
else
if $FAIL_INVALID_FMT || $VERBOSE; then
echo -e "${YELLOW}[Warning]:${NC} Invalid TODO format: $todo"
if $FAIL_INVALID_FMT; then
exit 1
fi
fi
((MISMATCH_COUNT++))
continue
fi
# Use GitHub API to fetch issue details
GH_URL_PATH="$REPO_FULL/issues/$ISSUE_NUM"
RESPONSE=$(curl -sL -H "$AUTH" --request GET "https://api.github.com/repos/$GH_URL_PATH")
# Check if issue was found
if echo "$RESPONSE" | rg -q "Not Found"; then
if [[ $VERBOSE ]]; then
echo -e "${YELLOW}[Warning]:${NC} Issue not found: ${RED}$REPO_FULL/$ISSUE_NUM${NC}"
fi
((NOT_FOUND_COUNT++))
continue
fi
# Check issue state
STATE=$(echo "$RESPONSE" | jq -r .state)
if [[ "$STATE" == "closed" ]]; then
echo -e "${RED}[Error]:${NC} Issue #$ISSUE_NUM is closed. Please remove the TODO in ${GREEN}$FILE:$LINE_NUM${NC} referencing ${YELLOW}$ISSUE_REFERENCE${NC} (${CYAN}https://github.com/$GH_URL_PATH${NC})"
exit 1
fi
((OPEN_COUNT++))
TITLE=$(echo "$RESPONSE" | jq -r .title)
OPEN_ISSUES+=("$REPO_FULL/issues/$ISSUE_NUM|$TITLE|$FILE:$LINE_NUM")
done
# Print summary
if [[ $NOT_FOUND_COUNT -gt 0 ]]; then
echo -e "${YELLOW}[Warning]:${NC} ${CYAN}$NOT_FOUND_COUNT${NC} TODOs referred to issues that were not found."
fi
if [[ $MISMATCH_COUNT -gt 0 ]]; then
echo -e "${YELLOW}[Warning]:${NC} ${CYAN}$MISMATCH_COUNT${NC} TODOs did not match the expected pattern. Run with ${RED}\`--verbose\`${NC} to show details."
fi
if [[ $OPEN_COUNT -gt 0 ]]; then
echo -e "${GREEN}[Info]:${NC} ${CYAN}$OPEN_COUNT${NC} TODOs refer to issues that are still open."
echo -e "${GREEN}[Info]:${NC} Open issue details:"
printf "\n${PURPLE}%-50s${NC} ${GREY}|${NC} ${GREEN}%-55s${NC} ${GREY}|${NC} ${YELLOW}%-30s${NC}\n" "Repository & Issue" "Title" "Location"
echo -e "$GREY$(printf '%0.s-' {1..51})+$(printf '%0.s-' {1..57})+$(printf '%0.s-' {1..31})$NC"
for issue in "${OPEN_ISSUES[@]}"; do
REPO_ISSUE="https://github.com/${issue%%|*}" # up to the first |
REMAINING="${issue#*|}" # after the first |
TITLE="${REMAINING%%|*}" # up to the second |
LOC="${REMAINING#*|}" # after the second |
# Truncate if necessary
if [ ${#REPO_ISSUE} -gt 47 ]; then
REPO_ISSUE=$(printf "%.47s..." "$REPO_ISSUE")
fi
if [ ${#TITLE} -gt 47 ]; then
TITLE=$(printf "%.52s..." "$TITLE")
fi
if [ ${#LOC} -gt 27 ]; then
LOC=$(printf "%.24s..." "$LOC")
fi
printf "${CYAN}%-50s${NC} ${GREY}|${NC} %-55s ${GREY}|${NC} ${YELLOW}%-30s${NC}\n" "$REPO_ISSUE" "$TITLE" "$LOC"
done
fi
echo -e "${GREEN}[Info]:${NC} Done checking issues."