blob: b0f56f3991bc3b3d2583a5ad0eee6c190506bf95 [file] [log] [blame]
Andrew Geissler9b0105c2018-01-10 10:58:35 -08001#!/bin/bash
Patrick Williamsc1513c82022-11-27 17:37:08 -06002set -e
Andrew Geissler9b0105c2018-01-10 10:58:35 -08003
Patrick Williamsc1513c82022-11-27 17:37:08 -06004# This script reformats source files using various formatters and linters.
Andrew Geissler9b0105c2018-01-10 10:58:35 -08005#
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#
Patrick Williamsc1513c82022-11-27 17:37:08 -060011function display_help()
12{
Patrick Williams816c7cc2022-12-08 05:51:57 -060013 echo "usage: format-code.sh [-h | --help] [--no-diff] [--list-tools]"
14 echo " [--disable <tool>] [--enable <tool>] [<path>]"
Patrick Williamsc1513c82022-11-27 17:37:08 -060015 echo
16 echo "Format and lint a repository."
17 echo
18 echo "Arguments:"
Patrick Williams147f37a2022-12-04 14:57:10 -060019 echo " --list-tools Display available linters and formatters"
20 echo " --no-diff Don't show final diff output"
21 echo " --disable <tool> Disable linter"
Patrick Williams816c7cc2022-12-08 05:51:57 -060022 echo " --enable <tool> Enable only specific linters"
Patrick Williams26d9d2c2022-12-05 15:03:07 -060023 echo " --allow-missing Run even if linters are not all present"
Patrick Williams147f37a2022-12-04 14:57:10 -060024 echo " path Path to git repository (default to pwd)"
Patrick Williamsc1513c82022-11-27 17:37:08 -060025}
Andrew Geissler9b0105c2018-01-10 10:58:35 -080026
Patrick Williams147f37a2022-12-04 14:57:10 -060027LINTERS_ALL=( \
28 commit_gitlint \
29 commit_spelling \
30 clang_format \
31 eslint \
Patrick Williamsc5ad7ff2022-12-05 10:21:40 -060032 flake8 \
Patrick Williams147f37a2022-12-04 14:57:10 -060033 shellcheck \
34 )
35LINTERS_DISABLED=()
36LINTERS_ENABLED=()
Patrick Williams785327a2022-12-05 16:20:43 -060037declare -A LINTERS_FAILED=()
Patrick Williams147f37a2022-12-04 14:57:10 -060038
Patrick Williams816c7cc2022-12-08 05:51:57 -060039eval set -- "$(getopt -o 'h' --long 'help,list-tools,no-diff,disable:,enable:,allow-missing' -n 'format-code.sh' -- "$@")"
Patrick Williamsc1513c82022-11-27 17:37:08 -060040while true; do
41 case "$1" in
42 '-h'|'--help')
43 display_help && exit 0
44 ;;
Andrew Geissler9b0105c2018-01-10 10:58:35 -080045
Patrick Williams147f37a2022-12-04 14:57:10 -060046 '--list-tools')
47 echo "Available tools:"
48 for t in "${LINTERS_ALL[@]}"; do
49 echo " $t"
50 done
51 exit 0
52 ;;
53
Patrick Williamsd27ab4c2022-11-27 17:51:35 -060054 '--no-diff')
55 OPTION_NO_DIFF=1
56 shift
57 ;;
58
Patrick Williams147f37a2022-12-04 14:57:10 -060059 '--disable')
60 LINTERS_DISABLED+=("$2")
61 shift && shift
62 ;;
63
Patrick Williams816c7cc2022-12-08 05:51:57 -060064 '--enable')
65 LINTERS_ENABLED+=("$2")
66 shift && shift
67 ;;
68
Patrick Williams26d9d2c2022-12-05 15:03:07 -060069 '--allow-missing')
70 ALLOW_MISSING=yes
71 shift
72 ;;
73
Patrick Williamsc1513c82022-11-27 17:37:08 -060074 '--')
75 shift
76 break
77 ;;
78
79 *)
80 echo "unknown option: $1"
81 display_help && exit 1
82 ;;
83 esac
84done
85
86# Detect tty and set nicer colors.
87if [ -t 1 ]; then
88 BLUE="\e[34m"
89 GREEN="\e[32m"
90 NORMAL="\e[0m"
91 RED="\e[31m"
92 YELLOW="\e[33m"
93else # non-tty, no escapes.
94 BLUE=""
95 GREEN=""
96 NORMAL=""
97 RED=""
98 YELLOW=""
99fi
100
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600101# Allow called scripts to know which clang format we are using
102export CLANG_FORMAT="clang-format"
103
Patrick Williamsc1513c82022-11-27 17:37:08 -0600104# Path to default config files for linters.
105CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config"
106
107# Find repository root for `pwd` or $1.
108if [ -z "$1" ]; then
109 DIR="$(git rev-parse --show-toplevel || pwd)"
110else
111 DIR="$(git -C "$1" rev-parse --show-toplevel)"
112fi
Patrick Williamsbc85b712022-12-05 15:16:27 -0600113if [ ! -e "$DIR/.git" ]; then
114 echo -e "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600115 exit 1
116fi
Andrew Jeffery0cce8482018-04-30 11:37:01 +0930117
Manojkiran Edae6f120a2021-03-20 11:13:43 +0530118cd "${DIR}"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600119echo -e " ${BLUE}Formatting code under${NORMAL} $DIR"
Andrew Geissler9b0105c2018-01-10 10:58:35 -0800120
Patrick Williams3d294942022-12-05 14:58:15 -0600121# Config hashes:
122# LINTER_REQUIRE - The requirements to run a linter, semi-colon separated.
123# 1. Executable.
124# 2. [optional] Configuration file.
125# 3. [optional] Global fallback configuration file.
126#
127# LINTER_IGNORE - An optional set of semi-colon separated ignore-files
128# specific to the linter.
129#
130# LINTER_TYPES - The file types supported by the linter, semi-colon separated.
131#
132# LINTER_CONFIG - The config (from LINTER_REQUIRE) chosen for the repository.
133#
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600134declare -A LINTER_REQUIRE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600135declare -A LINTER_IGNORE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600136declare -A LINTER_TYPES=()
Patrick Williams3d294942022-12-05 14:58:15 -0600137declare -A LINTER_CONFIG=()
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530138
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600139LINTER_REQUIRE+=([commit_spelling]="codespell")
Patrick Williams71b73242022-12-02 21:07:52 -0600140LINTER_TYPES+=([commit_spelling]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600141function do_commit_spelling() {
Patrick Williams38515a32022-11-27 06:58:56 -0600142 # Run the codespell with openbmc spcific spellings on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600143 echo -n "openbmc-dictionary - misspelling count >> "
144 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600145 codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count -
Patrick Williams38515a32022-11-27 06:58:56 -0600146
147 # Run the codespell with generic dictionary on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600148 echo -n "generic-dictionary - misspelling count >> "
149 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williams38515a32022-11-27 06:58:56 -0600150 codespell --builtin clear,rare,en-GB_to_en-US -d --count -
151}
152
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600153LINTER_REQUIRE+=([commit_gitlint]="gitlint")
Patrick Williams71b73242022-12-02 21:07:52 -0600154LINTER_TYPES+=([commit_gitlint]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600155function do_commit_gitlint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600156 gitlint --extra-path "${CONFIG_PATH}/gitlint/" \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600157 --config "${CONFIG_PATH}/.gitlint"
Patrick Williams38515a32022-11-27 06:58:56 -0600158}
159
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600160LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json")
Patrick Williams71b73242022-12-02 21:07:52 -0600161LINTER_IGNORE+=([eslint]=".eslintignore")
162LINTER_TYPES+=([eslint]="json")
Patrick Williams38515a32022-11-27 06:58:56 -0600163function do_eslint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600164 eslint --no-eslintrc -c "${LINTER_CONFIG[eslint]}" \
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600165 --ext .json --format=stylish \
166 --resolve-plugins-relative-to /usr/local/lib/node_modules \
Patrick Williams71b73242022-12-02 21:07:52 -0600167 --no-error-on-unmatched-pattern "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600168}
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530169
Patrick Williamsc5ad7ff2022-12-05 10:21:40 -0600170LINTER_REQUIRE+=([flake8]="flake8")
171LINTER_IGNORE+=([flake8]=".flake8-ignore")
172LINTER_TYPES+=([flake8]="python")
173function do_flake8() {
174 flake8 --show-source --extend-ignore=E203,E501 "$@"
175 # We disable E203 and E501 because 'black' is handling these and they
176 # disagree on best practices.
Patrick Williams38515a32022-11-27 06:58:56 -0600177}
James Feistf1665d62019-11-22 09:06:33 -0800178
Patrick Williamse0df3052022-12-05 10:22:00 -0600179LINTER_REQUIRE+=([shellcheck]="shellcheck")
180LINTER_IGNORE+=([shellcheck]=".shellcheck-ignore")
Patrick Williams71b73242022-12-02 21:07:52 -0600181LINTER_TYPES+=([shellcheck]="bash;sh")
Patrick Williams38515a32022-11-27 06:58:56 -0600182function do_shellcheck() {
Patrick Williams71b73242022-12-02 21:07:52 -0600183 shellcheck --color=never -x "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600184}
James Feistf1665d62019-11-22 09:06:33 -0800185
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600186LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format")
Patrick Williams71b73242022-12-02 21:07:52 -0600187LINTER_IGNORE+=([clang_format]=".clang-ignore;.clang-format-ignore")
188LINTER_TYPES+=([clang_format]="c;cpp")
Patrick Williams476a7e92022-12-06 09:52:53 -0600189function do_clang_format() {
Patrick Williams71b73242022-12-02 21:07:52 -0600190 "${CLANG_FORMAT}" -i "$@"
191}
Patrick Williams38515a32022-11-27 06:58:56 -0600192
Patrick Williams71b73242022-12-02 21:07:52 -0600193function get_file_type()
194{
195 case "$(basename "$1")" in
196 # First to early detect template files.
197 *.in | *.meson) echo "meson-template" && return ;;
198 *.mako | *.mako.*) echo "mako" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600199
Patrick Williams71b73242022-12-02 21:07:52 -0600200 *.ac) echo "autoconf" && return ;;
201 *.[ch]) echo "c" && return ;;
202 *.[ch]pp) echo "cpp" && return ;;
203 *.json) echo "json" && return ;;
204 *.md) echo "markdown" && return ;;
205 *.py) echo "python" && return ;;
Patrick Williams5095cbe2022-12-08 06:50:34 -0600206 *.tcl) echo "tcl" && return ;;
Patrick Williams71b73242022-12-02 21:07:52 -0600207 *.yaml | *.yml) echo "yaml" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600208
Patrick Williams71b73242022-12-02 21:07:52 -0600209 # Special files.
210 .git/COMMIT_EDITMSG) echo "commit" && return ;;
211 meson.build) echo "meson" && return ;;
212 esac
213
214 case "$(file "$1")" in
215 *Bourne-Again\ shell*) echo "bash" && return ;;
216 *C++\ source*) echo "cpp" && return ;;
217 *C\ source*) echo "c" && return ;;
218 *JSON\ data*) echo "json" && return ;;
219 *POSIX\ shell*) echo "sh" && return ;;
220 *Python\ script*) echo "python" && return ;;
Patrick Williamsd5d63952022-12-07 17:55:03 -0600221 *python3\ script*) echo "python" && return ;;
Patrick Williams71b73242022-12-02 21:07:52 -0600222 *zsh\ shell*) echo "zsh" && return ;;
223 esac
224
225 echo "unknown"
Patrick Williams38515a32022-11-27 06:58:56 -0600226}
227
Patrick Williams816c7cc2022-12-08 05:51:57 -0600228LINTERS_AVAILABLE=()
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600229function check_linter()
230{
231 TITLE="$1"
232 IFS=";" read -r -a ARGS <<< "$2"
233
Patrick Williams147f37a2022-12-04 14:57:10 -0600234 if [[ "${LINTERS_DISABLED[*]}" =~ $1 ]]; then
235 return
236 fi
237
Patrick Williams816c7cc2022-12-08 05:51:57 -0600238 if [ 0 -ne "${#LINTERS_ENABLED[@]}" ]; then
239 if ! [[ "${LINTERS_ENABLED[*]}" =~ $1 ]]; then
240 return
241 fi
242 fi
243
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600244 EXE="${ARGS[0]}"
245 if [ ! -x "${EXE}" ]; then
246 if ! which "${EXE}" > /dev/null 2>&1 ; then
247 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${EXE}"
Patrick Williams26d9d2c2022-12-05 15:03:07 -0600248 if [ -z "$ALLOW_MISSING" ]; then
249 exit 1
250 fi
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600251 return
252 fi
253 fi
254
255 CONFIG="${ARGS[1]}"
256 FALLBACK="${ARGS[2]}"
257
258 if [ -n "${CONFIG}" ]; then
259 if [ -e "${CONFIG}" ]; then
260 LINTER_CONFIG+=( [${TITLE}]="${CONFIG}" )
261 elif [ -n "${FALLBACK}" ] && [ -e "${FALLBACK}" ]; then
262 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${CONFIG}; using ${FALLBACK}"
263 LINTER_CONFIG+=( [${TITLE}]="${FALLBACK}" )
264 else
265 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find config ${CONFIG}"
266 return
267 fi
268 fi
269
Patrick Williams816c7cc2022-12-08 05:51:57 -0600270 LINTERS_AVAILABLE+=( "${TITLE}" )
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600271}
272
Patrick Williams71b73242022-12-02 21:07:52 -0600273# Check for a global .linter-ignore file.
274GLOBAL_IGNORE=("cat")
275if [ -e ".linter-ignore" ]; then
276 GLOBAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter" ".linter-ignore")
277fi
278
279# Find all the files in the git repository and organize by type.
280declare -A FILES=()
281if [ -e .git/COMMIT_EDITMSG ]; then
282 FILES+=([commit]=".git/COMMIT_EDITMSG")
283fi
284while read -r file; do
285 ftype="$(get_file_type "$file")"
286 FILES+=([$ftype]="$(echo -ne "$file;${FILES[$ftype]:-}")")
287done < <(git ls-files | "${GLOBAL_IGNORE[@]}")
288
289# For each linter, check if there are an applicable files and if it can
290# be enabled.
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600291for op in "${LINTERS_ALL[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600292 for ftype in ${LINTER_TYPES[$op]//;/ }; do
293 if [[ -v FILES["$ftype"] ]]; then
294 check_linter "$op" "${LINTER_REQUIRE[${op}]}"
295 break
296 fi
297 done
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600298done
299
Patrick Williams71b73242022-12-02 21:07:52 -0600300# Call each linter.
Patrick Williams816c7cc2022-12-08 05:51:57 -0600301for op in "${LINTERS_AVAILABLE[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600302
303 # Determine the linter-specific ignore file(s).
304 LOCAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter")
305 if [[ -v LINTER_IGNORE["$op"] ]]; then
306 for ignorefile in ${LINTER_IGNORE["$op"]//;/ } ; do
307 if [ -e "$ignorefile" ]; then
308 LOCAL_IGNORE+=("$ignorefile")
309 fi
310 done
311 fi
312 if [ 1 -eq ${#LOCAL_IGNORE[@]} ]; then
313 LOCAL_IGNORE=("cat")
314 fi
315
316 # Find all the files for this linter, filtering out the ignores.
317 LINTER_FILES=()
318 while read -r file ; do
319 if [ -e "$file" ]; then
320 LINTER_FILES+=("$file")
321 fi
322 done < <(for ftype in ${LINTER_TYPES[$op]//;/ }; do
323 # shellcheck disable=SC2001
324 echo "${FILES["$ftype"]:-}" | sed "s/;/\\n/g"
325 done | "${LOCAL_IGNORE[@]}")
326
327 # Call the linter now with all the files.
Patrick Williams200ec182022-12-06 08:32:03 -0600328 if [ 0 -ne ${#LINTER_FILES[@]} ]; then
329 echo -e " ${BLUE}Running $op${NORMAL}"
330 if ! "do_$op" "${LINTER_FILES[@]}" ; then
331 LINTERS_FAILED+=([$op]=1)
332 fi
333 else
334 echo -e " ${YELLOW}${op}:${NORMAL} all applicable files are on ignore-lists"
Patrick Williams785327a2022-12-05 16:20:43 -0600335 fi
Patrick Williams38515a32022-11-27 06:58:56 -0600336done
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030337
Patrick Williams785327a2022-12-05 16:20:43 -0600338# Check for failing linters.
339if [ 0 -ne ${#LINTERS_FAILED[@]} ]; then
340 for op in "${!LINTERS_FAILED[@]}"; do
341 echo -e "$op: ${RED}FAILED${NORMAL}"
342 done
343 exit 1
344fi
345
Patrick Williams71b73242022-12-02 21:07:52 -0600346# Check for differences.
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600347if [ -z "$OPTION_NO_DIFF" ]; then
348 echo -e " ${BLUE}Result differences...${NORMAL}"
349 if ! git --no-pager diff --exit-code ; then
350 echo -e "Format: ${RED}FAILED${NORMAL}"
351 exit 1
352 else
353 echo -e "Format: ${GREEN}PASSED${NORMAL}"
354 fi
Patrick Williamsc1513c82022-11-27 17:37:08 -0600355fi
356
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030357# Sometimes your situation is terrible enough that you need the flexibility.
358# For example, phosphor-mboxd.
Patrick Williams330b0772022-11-28 06:14:06 -0600359for formatter in "format-code.sh" "format-code"; do
360 if [[ -x "${formatter}" ]]; then
361 echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}"
362 "./${formatter}"
363 if [ -z "$OPTION_NO_DIFF" ]; then
364 git --no-pager diff --exit-code
365 fi
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600366 fi
Patrick Williams330b0772022-11-28 06:14:06 -0600367done