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# Allow called scripts to know which clang format we are using 63export CLANG_FORMAT="clang-format" 64 65# Path to default config files for linters. 66CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config" 67 68# Find repository root for `pwd` or $1. 69if [ -z "$1" ]; then 70 DIR="$(git rev-parse --show-toplevel || pwd)" 71else 72 DIR="$(git -C "$1" rev-parse --show-toplevel)" 73fi 74if [ ! -e "$DIR/.git" ]; then 75 echo -e "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository" 76 exit 1 77fi 78 79cd "${DIR}" 80echo -e " ${BLUE}Formatting code under${NORMAL} $DIR" 81 82LINTERS_ALL=( \ 83 commit_gitlint \ 84 commit_spelling \ 85 clang_format \ 86 eslint \ 87 pycodestyle \ 88 shellcheck \ 89 ) 90declare -A LINTER_REQUIRE=() 91declare -A LINTER_CONFIG=() 92LINTERS_ENABLED=() 93 94LINTER_REQUIRE+=([commit_spelling]="codespell") 95function do_commit_spelling() { 96 if [ ! -e .git/COMMIT_EDITMSG ]; then 97 return 98 fi 99 echo -e " ${BLUE}Running codespell${NORMAL}" 100 101 # Run the codespell with openbmc spcific spellings on the patchset 102 echo "openbmc-dictionary - misspelling count >> " 103 sed "s/Signed-off-by.*//" .git/COMMIT_EDITMSG | \ 104 codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count - 105 106 # Run the codespell with generic dictionary on the patchset 107 echo "generic-dictionary - misspelling count >> " 108 sed "s/Signed-off-by.*//" .git/COMMIT_EDITMSG | \ 109 codespell --builtin clear,rare,en-GB_to_en-US -d --count - 110} 111 112LINTER_REQUIRE+=([commit_gitlint]="gitlint") 113function do_commit_gitlint() { 114 echo -e " ${BLUE}Running gitlint${NORMAL}" 115 # Check for commit message issues 116 gitlint \ 117 --extra-path "${CONFIG_PATH}/gitlint/" \ 118 --config "${CONFIG_PATH}/.gitlint" 119} 120 121LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json") 122function do_eslint() { 123 echo -e " ${BLUE}Running eslint${NORMAL}" 124 125 if [[ -f ".eslintignore" ]]; then 126 ESLINT_IGNORE="--ignore-path=.eslintignore" 127 elif [[ -f ".gitignore" ]]; then 128 ESLINT_IGNORE="--ignore-path=.gitignore" 129 fi 130 131 eslint . "${ESLINT_IGNORE}" --no-eslintrc -c "${LINTER_CONFIG[eslint]}" \ 132 --ext .json --format=stylish \ 133 --resolve-plugins-relative-to /usr/local/lib/node_modules \ 134 --no-error-on-unmatched-pattern 135} 136 137LINTER_REQUIRE+=([pycodestyle]="pycodestyle;setup.cfg") 138function do_pycodestyle() { 139 echo -e " ${BLUE}Running pycodestyle${NORMAL}" 140 pycodestyle --show-source --exclude=subprojects . 141 rc=$? 142 if [[ ${rc} -ne 0 ]]; then 143 exit ${rc} 144 fi 145} 146 147LINTER_REQUIRE+=([shellcheck]="shellcheck;.shellcheck") 148function do_shellcheck() { 149 # Run shellcheck on any shell-script. 150 shell_scripts="$(git ls-files | xargs -n1 file -0 | \ 151 grep -a "shell script" | cut -d '' -f 1)" 152 if [ -n "${shell_scripts}" ]; then 153 echo -e " ${BLUE}Running shellcheck${NORMAL}" 154 fi 155 for script in ${shell_scripts}; do 156 shellcheck --color=never -x "${script}" 157 done 158} 159 160LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format") 161do_clang_format() { 162 163 echo -e " ${BLUE}Running clang-format${NORMAL}" 164 files=$(git ls-files | \ 165 grep -e '\.[ch]pp$' -e '\.[ch]$' | \ 166 grep -v '\.mako\.') 167 168 if [ -e .clang-ignore ]; then 169 files=$("${CONFIG_PATH}/lib/ignore-filter" .clang-ignore <<< "${files}") 170 fi 171 172 xargs "${CLANG_FORMAT}" -i <<< "${files}" 173} 174 175function check_linter() 176{ 177 TITLE="$1" 178 IFS=";" read -r -a ARGS <<< "$2" 179 180 EXE="${ARGS[0]}" 181 if [ ! -x "${EXE}" ]; then 182 if ! which "${EXE}" > /dev/null 2>&1 ; then 183 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${EXE}" 184 return 185 fi 186 fi 187 188 CONFIG="${ARGS[1]}" 189 FALLBACK="${ARGS[2]}" 190 191 if [ -n "${CONFIG}" ]; then 192 if [ -e "${CONFIG}" ]; then 193 LINTER_CONFIG+=( [${TITLE}]="${CONFIG}" ) 194 elif [ -n "${FALLBACK}" ] && [ -e "${FALLBACK}" ]; then 195 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${CONFIG}; using ${FALLBACK}" 196 LINTER_CONFIG+=( [${TITLE}]="${FALLBACK}" ) 197 else 198 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find config ${CONFIG}" 199 return 200 fi 201 fi 202 203 LINTERS_ENABLED+=( "${TITLE}" ) 204} 205 206for op in "${LINTERS_ALL[@]}"; do 207 check_linter "$op" "${LINTER_REQUIRE[${op}]}" 208done 209 210for op in "${LINTERS_ENABLED[@]}"; do 211 "do_$op" 212done 213 214if [ -z "$OPTION_NO_DIFF" ]; then 215 echo -e " ${BLUE}Result differences...${NORMAL}" 216 if ! git --no-pager diff --exit-code ; then 217 echo -e "Format: ${RED}FAILED${NORMAL}" 218 exit 1 219 else 220 echo -e "Format: ${GREEN}PASSED${NORMAL}" 221 fi 222fi 223 224# Sometimes your situation is terrible enough that you need the flexibility. 225# For example, phosphor-mboxd. 226for formatter in "format-code.sh" "format-code"; do 227 if [[ -x "${formatter}" ]]; then 228 echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}" 229 "./${formatter}" 230 if [ -z "$OPTION_NO_DIFF" ]; then 231 git --no-pager diff --exit-code 232 fi 233 fi 234done 235