todo-checker.sh 5.13 KB
Newer Older
clabby's avatar
clabby committed
1 2
#!/bin/bash

clabby's avatar
clabby committed
3 4 5 6 7 8
set -uo pipefail

# Flags
FAIL_INVALID_FMT=false
VERBOSE=false

clabby's avatar
clabby committed
9
# Github API access token (Optional - necessary for private repositories.)
clabby's avatar
clabby committed
10
GH_API_TOKEN="${CI_TODO_CHECKER_PAT:-""}"
clabby's avatar
clabby committed
11 12
AUTH=""
if [[ $GH_API_TOKEN != "" ]]; then
clabby's avatar
clabby committed
13
    AUTH="Authorization: token $GH_API_TOKEN"
clabby's avatar
clabby committed
14 15 16 17
fi

# Default org and repo
ORG="ethereum-optimism"
clabby's avatar
clabby committed
18
REPO="optimism"
clabby's avatar
clabby committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

# 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

clabby's avatar
clabby committed
35 36 37 38
# 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
clabby's avatar
clabby committed
39 40 41 42 43 44 45 46 47 48 49 50 51
for arg in "$@"; do
  case $arg in
    --strict)
    FAIL_INVALID_FMT=true
    shift
    ;;
    --verbose)
    VERBOSE=true
    shift
    ;;
  esac
done

clabby's avatar
clabby committed
52
# Use ripgrep to search for the pattern in all files within the repo
53
todos=$(rg -o --with-filename -i -n -g '!ops/scripts/todo-checker.sh' 'TODO\(([^)]+)\): [^,;]*')
clabby's avatar
clabby committed
54 55 56 57 58

# 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
59 60 61
    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')
clabby's avatar
clabby committed
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

    # 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
clabby's avatar
clabby committed
85
            fi
clabby's avatar
clabby committed
86
        fi
clabby's avatar
clabby committed
87 88 89
        ((MISMATCH_COUNT++))
        continue
    fi
clabby's avatar
clabby committed
90

clabby's avatar
clabby committed
91 92 93
    # 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")
clabby's avatar
clabby committed
94

clabby's avatar
clabby committed
95 96 97 98
    # 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}"
clabby's avatar
clabby committed
99
        fi
clabby's avatar
clabby committed
100 101 102
        ((NOT_FOUND_COUNT++))
        continue
    fi
clabby's avatar
clabby committed
103

clabby's avatar
clabby committed
104 105
    # Check issue state
    STATE=$(echo "$RESPONSE" | jq -r .state)
clabby's avatar
clabby committed
106

clabby's avatar
clabby committed
107 108 109
    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
clabby's avatar
clabby committed
110
    fi
clabby's avatar
clabby committed
111

clabby's avatar
clabby committed
112 113 114 115
    ((OPEN_COUNT++))
    TITLE=$(echo "$RESPONSE" | jq -r .title)
    OPEN_ISSUES+=("$REPO_FULL/issues/$ISSUE_NUM|$TITLE|$FILE:$LINE_NUM")
done
clabby's avatar
clabby committed
116

clabby's avatar
clabby committed
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
# 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
clabby's avatar
clabby committed
145

clabby's avatar
clabby committed
146 147 148
        printf "${CYAN}%-50s${NC} ${GREY}|${NC} %-55s ${GREY}|${NC} ${YELLOW}%-30s${NC}\n" "$REPO_ISSUE" "$TITLE" "$LOC"
    done
fi
clabby's avatar
clabby committed
149

clabby's avatar
clabby committed
150
echo -e "${GREEN}[Info]:${NC} Done checking issues."