1#!/bin/bash 2set -e 3 4# This script reformats source files using various formatters and linters. 5# 6# Files are changed in-place, so make sure you don't have anything open in an 7# editor, and you may want to commit before formatting in case of awryness. 8# 9# This must be run on a clean repository to succeed 10# 11function display_help() 12{ 13 echo "usage: format-code.sh [-h | --help] [--no-diff]" 14 echo " [<path>]" 15 echo 16 echo "Format and lint a repository." 17 echo 18 echo "Arguments:" 19 echo " --no-diff Don't show final diff output" 20 echo " path Path to git repository (default to pwd)" 21} 22 23eval set -- "$(getopt -o 'h' --long 'help,no-diff' -n 'format-code.sh' -- "$@")" 24while true; do 25 case "$1" in 26 '-h'|'--help') 27 display_help && exit 0 28 ;; 29 30 '--no-diff') 31 OPTION_NO_DIFF=1 32 shift 33 ;; 34 35 '--') 36 shift 37 break 38 ;; 39 40 *) 41 echo "unknown option: $1" 42 display_help && exit 1 43 ;; 44 esac 45done 46 47# Detect tty and set nicer colors. 48if [ -t 1 ]; then 49 BLUE="\e[34m" 50 GREEN="\e[32m" 51 NORMAL="\e[0m" 52 RED="\e[31m" 53 YELLOW="\e[33m" 54else # non-tty, no escapes. 55 BLUE="" 56 GREEN="" 57 NORMAL="" 58 RED="" 59 YELLOW="" 60fi 61 62# Path to default config files for linters. 63CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config" 64 65# Find repository root for `pwd` or $1. 66if [ -z "$1" ]; then 67 DIR="$(git rev-parse --show-toplevel || pwd)" 68else 69 DIR="$(git -C "$1" rev-parse --show-toplevel)" 70fi 71if [ ! -d "$DIR/.git" ]; then 72 echo "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository" 73 exit 1 74fi 75 76cd "${DIR}" 77echo -e " ${BLUE}Formatting code under${NORMAL} $DIR" 78 79ALL_OPERATIONS=( \ 80 commit_gitlint \ 81 commit_spelling \ 82 clang_format \ 83 eslint \ 84 pycodestyle \ 85 shellcheck \ 86 ) 87 88function do_commit_spelling() { 89 if [ ! -e .git/COMMIT_EDITMSG ]; then 90 return 91 fi 92 echo -e " ${BLUE}Running codespell${NORMAL}" 93 94 # Run the codespell with openbmc spcific spellings on the patchset 95 echo "openbmc-dictionary - misspelling count >> " 96 sed "s/Signed-off-by.*//" .git/COMMIT_EDITMSG | \ 97 codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count - 98 99 # Run the codespell with generic dictionary on the patchset 100 echo "generic-dictionary - misspelling count >> " 101 sed "s/Signed-off-by.*//" .git/COMMIT_EDITMSG | \ 102 codespell --builtin clear,rare,en-GB_to_en-US -d --count - 103} 104 105function do_commit_gitlint() { 106 echo -e " ${BLUE}Running gitlint${NORMAL}" 107 # Check for commit message issues 108 gitlint \ 109 --extra-path "${CONFIG_PATH}/gitlint/" \ 110 --config "${CONFIG_PATH}/.gitlint" 111} 112 113function do_eslint() { 114 if [[ -f ".eslintignore" ]]; then 115 ESLINT_IGNORE="--ignore-path .eslintignore" 116 elif [[ -f ".gitignore" ]]; then 117 ESLINT_IGNORE="--ignore-path .gitignore" 118 fi 119 120 # Get the eslint configuration from the repository 121 if [[ -f ".eslintrc.json" ]]; then 122 echo -e " ${BLUE}Running eslint${NORMAL}" 123 ESLINT_RC="-c .eslintrc.json" 124 else 125 echo -e " ${BLUE}Running eslint using ${YELLOW}the global config${NORMAL}" 126 ESLINT_RC="--no-eslintrc -c ${CONFIG_PATH}/eslint-global-config.json" 127 fi 128 129 ESLINT_COMMAND="eslint . ${ESLINT_IGNORE} ${ESLINT_RC} \ 130 --ext .json --format=stylish \ 131 --resolve-plugins-relative-to /usr/local/lib/node_modules \ 132 --no-error-on-unmatched-pattern" 133 134 # Print eslint command 135 echo "$ESLINT_COMMAND" 136 # Run eslint 137 $ESLINT_COMMAND 138} 139 140function do_pycodestyle() { 141 if [[ -f "setup.cfg" ]]; then 142 echo -e " ${BLUE}Running pycodestyle${NORMAL}" 143 pycodestyle --show-source --exclude=subprojects . 144 rc=$? 145 if [[ ${rc} -ne 0 ]]; then 146 exit ${rc} 147 fi 148 fi 149} 150 151function do_shellcheck() { 152 # If .shellcheck exists, stop on error. Otherwise, allow pass. 153 if [[ -f ".shellcheck" ]]; then 154 local shellcheck_allowfail="false" 155 else 156 local shellcheck_allowfail="true" 157 fi 158 159 # Run shellcheck on any shell-script. 160 shell_scripts="$(git ls-files | xargs -n1 file -0 | \ 161 grep -a "shell script" | cut -d '' -f 1)" 162 if [ -n "${shell_scripts}" ]; then 163 echo -e " ${BLUE}Running shellcheck${NORMAL}" 164 fi 165 for script in ${shell_scripts}; do 166 shellcheck --color=never -x "${script}" || ${shellcheck_allowfail} 167 done 168} 169 170 171do_clang_format() { 172 # Allow called scripts to know which clang format we are using 173 export CLANG_FORMAT="clang-format" 174 IGNORE_FILE=".clang-ignore" 175 declare -a IGNORE_LIST 176 177 if [[ -f "${IGNORE_FILE}" ]]; then 178 readarray -t IGNORE_LIST < "${IGNORE_FILE}" 179 fi 180 181 ignorepaths="" 182 ignorefiles="" 183 184 for path in "${IGNORE_LIST[@]}"; do 185 # Check for comment, line starting with space, or zero-length string. 186 # Checking for [[:space:]] checks all options. 187 if [[ -z "${path}" ]] || [[ "${path}" =~ ^(#|[[:space:]]).*$ ]]; then 188 continue 189 fi 190 191 # All paths must start with ./ for find's path prune expectation. 192 if [[ "${path}" =~ ^\.\/.+$ ]]; then 193 ignorepaths+=" ${path}" 194 else 195 ignorefiles+=" ${path}" 196 fi 197 done 198 199 searchfiles="" 200 while read -r path; do 201 # skip ignorefiles 202 if [[ $ignorefiles == *"$(basename "${path}")"* ]]; then 203 continue 204 fi 205 206 skip=false 207 #skip paths in ingorepaths 208 for pathname in $ignorepaths; do 209 if [[ "./${path}" == "${pathname}"* ]]; then 210 skip=true 211 break 212 fi 213 done 214 215 if [ "$skip" = true ]; then 216 continue 217 fi 218 # shellcheck disable=2089 219 searchfiles+="\"./${path}\" " 220 221 # Get C and C++ files managed by git and skip the mako files 222 done <<<"$(git ls-files | grep -e '\.[ch]pp$' -e '\.[ch]$' | grep -v '\.mako\.')" 223 224 if [[ -f ".clang-format" ]]; then 225 echo -e " ${BLUE}Running clang-format${NORMAL}" 226 # shellcheck disable=SC2090 disable=SC2086 227 echo ${searchfiles} | xargs "${CLANG_FORMAT}" -i 228 fi 229 230} 231 232for op in "${ALL_OPERATIONS[@]}"; do 233 "do_$op" 234done 235 236if [ -z "$OPTION_NO_DIFF" ]; then 237 echo -e " ${BLUE}Result differences...${NORMAL}" 238 if ! git --no-pager diff --exit-code ; then 239 echo -e "Format: ${RED}FAILED${NORMAL}" 240 exit 1 241 else 242 echo -e "Format: ${GREEN}PASSED${NORMAL}" 243 fi 244fi 245 246# Sometimes your situation is terrible enough that you need the flexibility. 247# For example, phosphor-mboxd. 248for formatter in "format-code.sh" "format-code"; do 249 if [[ -x "${formatter}" ]]; then 250 echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}" 251 "./${formatter}" 252 if [ -z "$OPTION_NO_DIFF" ]; then 253 git --no-pager diff --exit-code 254 fi 255 fi 256done 257