blob: 847084d3b9622b6cb247c0c56b6e77752dac4cdc [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 \
Patrick Williamsc5969592022-12-06 09:50:25 -060030 beautysh \
31 beautysh_sh \
Patrick Williamse795dfe2022-12-06 10:07:02 -060032 black \
Patrick Williams147f37a2022-12-04 14:57:10 -060033 clang_format \
34 eslint \
Patrick Williamsc5ad7ff2022-12-05 10:21:40 -060035 flake8 \
Patrick Williamse795dfe2022-12-06 10:07:02 -060036 isort \
Patrick Williams7d41f6d2022-12-06 10:19:43 -060037 markdownlint \
Patrick Williamsb08ddf72022-12-06 08:56:31 -060038 prettier \
Patrick Williams147f37a2022-12-04 14:57:10 -060039 shellcheck \
40 )
41LINTERS_DISABLED=()
42LINTERS_ENABLED=()
Patrick Williams785327a2022-12-05 16:20:43 -060043declare -A LINTERS_FAILED=()
Patrick Williams147f37a2022-12-04 14:57:10 -060044
Patrick Williams816c7cc2022-12-08 05:51:57 -060045eval 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 -060046while true; do
47 case "$1" in
48 '-h'|'--help')
49 display_help && exit 0
50 ;;
Andrew Geissler9b0105c2018-01-10 10:58:35 -080051
Patrick Williams147f37a2022-12-04 14:57:10 -060052 '--list-tools')
53 echo "Available tools:"
54 for t in "${LINTERS_ALL[@]}"; do
55 echo " $t"
56 done
57 exit 0
58 ;;
59
Patrick Williamsd27ab4c2022-11-27 17:51:35 -060060 '--no-diff')
61 OPTION_NO_DIFF=1
62 shift
63 ;;
64
Patrick Williams147f37a2022-12-04 14:57:10 -060065 '--disable')
66 LINTERS_DISABLED+=("$2")
67 shift && shift
68 ;;
69
Patrick Williams816c7cc2022-12-08 05:51:57 -060070 '--enable')
71 LINTERS_ENABLED+=("$2")
72 shift && shift
73 ;;
74
Patrick Williams26d9d2c2022-12-05 15:03:07 -060075 '--allow-missing')
76 ALLOW_MISSING=yes
77 shift
78 ;;
79
Patrick Williamsc1513c82022-11-27 17:37:08 -060080 '--')
81 shift
82 break
83 ;;
84
85 *)
86 echo "unknown option: $1"
87 display_help && exit 1
88 ;;
89 esac
90done
91
92# Detect tty and set nicer colors.
93if [ -t 1 ]; then
94 BLUE="\e[34m"
95 GREEN="\e[32m"
96 NORMAL="\e[0m"
97 RED="\e[31m"
98 YELLOW="\e[33m"
99else # non-tty, no escapes.
100 BLUE=""
101 GREEN=""
102 NORMAL=""
103 RED=""
104 YELLOW=""
105fi
106
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600107# Allow called scripts to know which clang format we are using
108export CLANG_FORMAT="clang-format"
109
Patrick Williamsc1513c82022-11-27 17:37:08 -0600110# Path to default config files for linters.
111CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config"
112
113# Find repository root for `pwd` or $1.
114if [ -z "$1" ]; then
115 DIR="$(git rev-parse --show-toplevel || pwd)"
116else
117 DIR="$(git -C "$1" rev-parse --show-toplevel)"
118fi
Patrick Williamsbc85b712022-12-05 15:16:27 -0600119if [ ! -e "$DIR/.git" ]; then
120 echo -e "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600121 exit 1
122fi
Andrew Jeffery0cce8482018-04-30 11:37:01 +0930123
Manojkiran Edae6f120a2021-03-20 11:13:43 +0530124cd "${DIR}"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600125echo -e " ${BLUE}Formatting code under${NORMAL} $DIR"
Andrew Geissler9b0105c2018-01-10 10:58:35 -0800126
Patrick Williams3d294942022-12-05 14:58:15 -0600127# Config hashes:
128# LINTER_REQUIRE - The requirements to run a linter, semi-colon separated.
129# 1. Executable.
130# 2. [optional] Configuration file.
131# 3. [optional] Global fallback configuration file.
132#
133# LINTER_IGNORE - An optional set of semi-colon separated ignore-files
134# specific to the linter.
135#
136# LINTER_TYPES - The file types supported by the linter, semi-colon separated.
137#
138# LINTER_CONFIG - The config (from LINTER_REQUIRE) chosen for the repository.
139#
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600140declare -A LINTER_REQUIRE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600141declare -A LINTER_IGNORE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600142declare -A LINTER_TYPES=()
Patrick Williams3d294942022-12-05 14:58:15 -0600143declare -A LINTER_CONFIG=()
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530144
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600145LINTER_REQUIRE+=([commit_spelling]="codespell")
Patrick Williams71b73242022-12-02 21:07:52 -0600146LINTER_TYPES+=([commit_spelling]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600147function do_commit_spelling() {
Patrick Williams38515a32022-11-27 06:58:56 -0600148 # Run the codespell with openbmc spcific spellings on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600149 echo -n "openbmc-dictionary - misspelling count >> "
150 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600151 codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count -
Patrick Williams38515a32022-11-27 06:58:56 -0600152
153 # Run the codespell with generic dictionary on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600154 echo -n "generic-dictionary - misspelling count >> "
155 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williams38515a32022-11-27 06:58:56 -0600156 codespell --builtin clear,rare,en-GB_to_en-US -d --count -
157}
158
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600159LINTER_REQUIRE+=([commit_gitlint]="gitlint")
Patrick Williams71b73242022-12-02 21:07:52 -0600160LINTER_TYPES+=([commit_gitlint]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600161function do_commit_gitlint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600162 gitlint --extra-path "${CONFIG_PATH}/gitlint/" \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600163 --config "${CONFIG_PATH}/.gitlint"
Patrick Williams38515a32022-11-27 06:58:56 -0600164}
165
Patrick Williamsc5969592022-12-06 09:50:25 -0600166# We need different function style for bash/zsh vs plain sh, so beautysh is
167# split into two linters. "function foo()" is not traditionally accepted
168# POSIX-shell syntax, so shellcheck barfs on it.
169LINTER_REQUIRE+=([beautysh]="beautysh")
170LINTER_IGNORE+=([beautysh]=".beautysh-ignore")
171LINTER_TYPES+=([beautysh]="bash;zsh")
172function do_beautysh() {
173 beautysh --force-function-style fnpar "$@"
174}
175LINTER_REQUIRE+=([beautysh_sh]="beautysh")
176LINTER_IGNORE+=([beautysh_sh]=".beautysh-ignore")
177LINTER_TYPES+=([beautysh_sh]="sh")
178function do_beautysh_sh() {
179 beautysh --force-function-style paronly "$@"
180}
181
Patrick Williamse795dfe2022-12-06 10:07:02 -0600182LINTER_REQUIRE+=([black]="black")
183LINTER_TYPES+=([black]="python")
184function do_black() {
Patrick Williams45b21522023-02-14 16:39:20 -0600185 black -l 79 "$@"
Patrick Williamse795dfe2022-12-06 10:07:02 -0600186}
187
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600188LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json")
Patrick Williams71b73242022-12-02 21:07:52 -0600189LINTER_IGNORE+=([eslint]=".eslintignore")
190LINTER_TYPES+=([eslint]="json")
Patrick Williams38515a32022-11-27 06:58:56 -0600191function do_eslint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600192 eslint --no-eslintrc -c "${LINTER_CONFIG[eslint]}" \
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600193 --ext .json --format=stylish \
194 --resolve-plugins-relative-to /usr/local/lib/node_modules \
Patrick Williams71b73242022-12-02 21:07:52 -0600195 --no-error-on-unmatched-pattern "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600196}
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530197
Patrick Williamsc5ad7ff2022-12-05 10:21:40 -0600198LINTER_REQUIRE+=([flake8]="flake8")
199LINTER_IGNORE+=([flake8]=".flake8-ignore")
200LINTER_TYPES+=([flake8]="python")
201function do_flake8() {
202 flake8 --show-source --extend-ignore=E203,E501 "$@"
203 # We disable E203 and E501 because 'black' is handling these and they
204 # disagree on best practices.
Patrick Williams38515a32022-11-27 06:58:56 -0600205}
James Feistf1665d62019-11-22 09:06:33 -0800206
Patrick Williamse795dfe2022-12-06 10:07:02 -0600207LINTER_REQUIRE+=([isort]="isort")
208LINTER_TYPES+=([isort]="python")
209function do_isort() {
210 isort --profile black "$@"
211}
212
Patrick Williams7d41f6d2022-12-06 10:19:43 -0600213LINTER_REQUIRE+=([markdownlint]="markdownlint;.markdownlint.yaml;${CONFIG_PATH}/markdownlint.yaml")
214LINTER_IGNORE+=([markdownlint]=".markdownlint-ignore")
215LINTER_TYPES+=([markdownlint]="markdown")
216function do_markdownlint() {
217 markdownlint --config "${LINTER_CONFIG[markdownlint]}" \
218 --disable line-length -- "$@" || \
219 echo -e " ${YELLOW}Failed markdownlint; temporarily ignoring."
220 # We disable line-length because prettier should handle prose wrap for us.
221}
222
Patrick Williamsb08ddf72022-12-06 08:56:31 -0600223LINTER_REQUIRE+=([prettier]="prettier;.prettierrc.yaml;${CONFIG_PATH}/prettierrc.yaml")
224LINTER_IGNORE+=([prettier]=".prettierignore")
225LINTER_TYPES+=([prettier]="json;markdown;yaml")
226function do_prettier() {
227 prettier --config "${LINTER_CONFIG[prettier]}" --write "$@"
228}
229
Patrick Williamse0df3052022-12-05 10:22:00 -0600230LINTER_REQUIRE+=([shellcheck]="shellcheck")
231LINTER_IGNORE+=([shellcheck]=".shellcheck-ignore")
Patrick Williams71b73242022-12-02 21:07:52 -0600232LINTER_TYPES+=([shellcheck]="bash;sh")
Patrick Williams38515a32022-11-27 06:58:56 -0600233function do_shellcheck() {
Patrick Williams71b73242022-12-02 21:07:52 -0600234 shellcheck --color=never -x "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600235}
James Feistf1665d62019-11-22 09:06:33 -0800236
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600237LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format")
Patrick Williams71b73242022-12-02 21:07:52 -0600238LINTER_IGNORE+=([clang_format]=".clang-ignore;.clang-format-ignore")
239LINTER_TYPES+=([clang_format]="c;cpp")
Patrick Williams476a7e92022-12-06 09:52:53 -0600240function do_clang_format() {
Patrick Williams71b73242022-12-02 21:07:52 -0600241 "${CLANG_FORMAT}" -i "$@"
242}
Patrick Williams38515a32022-11-27 06:58:56 -0600243
Patrick Williams71b73242022-12-02 21:07:52 -0600244function get_file_type()
245{
246 case "$(basename "$1")" in
247 # First to early detect template files.
248 *.in | *.meson) echo "meson-template" && return ;;
249 *.mako | *.mako.*) echo "mako" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600250
Patrick Williams71b73242022-12-02 21:07:52 -0600251 *.ac) echo "autoconf" && return ;;
252 *.[ch]) echo "c" && return ;;
253 *.[ch]pp) echo "cpp" && return ;;
254 *.json) echo "json" && return ;;
255 *.md) echo "markdown" && return ;;
256 *.py) echo "python" && return ;;
Patrick Williams5095cbe2022-12-08 06:50:34 -0600257 *.tcl) echo "tcl" && return ;;
Patrick Williams71b73242022-12-02 21:07:52 -0600258 *.yaml | *.yml) echo "yaml" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600259
Patrick Williams71b73242022-12-02 21:07:52 -0600260 # Special files.
261 .git/COMMIT_EDITMSG) echo "commit" && return ;;
262 meson.build) echo "meson" && return ;;
263 esac
264
265 case "$(file "$1")" in
266 *Bourne-Again\ shell*) echo "bash" && return ;;
267 *C++\ source*) echo "cpp" && return ;;
268 *C\ source*) echo "c" && return ;;
269 *JSON\ data*) echo "json" && return ;;
270 *POSIX\ shell*) echo "sh" && return ;;
271 *Python\ script*) echo "python" && return ;;
Patrick Williamsd5d63952022-12-07 17:55:03 -0600272 *python3\ script*) echo "python" && return ;;
Patrick Williams71b73242022-12-02 21:07:52 -0600273 *zsh\ shell*) echo "zsh" && return ;;
274 esac
275
276 echo "unknown"
Patrick Williams38515a32022-11-27 06:58:56 -0600277}
278
Patrick Williams816c7cc2022-12-08 05:51:57 -0600279LINTERS_AVAILABLE=()
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600280function check_linter()
281{
282 TITLE="$1"
283 IFS=";" read -r -a ARGS <<< "$2"
284
Patrick Williams147f37a2022-12-04 14:57:10 -0600285 if [[ "${LINTERS_DISABLED[*]}" =~ $1 ]]; then
286 return
287 fi
288
Patrick Williams816c7cc2022-12-08 05:51:57 -0600289 if [ 0 -ne "${#LINTERS_ENABLED[@]}" ]; then
290 if ! [[ "${LINTERS_ENABLED[*]}" =~ $1 ]]; then
291 return
292 fi
293 fi
294
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600295 EXE="${ARGS[0]}"
296 if [ ! -x "${EXE}" ]; then
297 if ! which "${EXE}" > /dev/null 2>&1 ; then
298 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${EXE}"
Patrick Williams26d9d2c2022-12-05 15:03:07 -0600299 if [ -z "$ALLOW_MISSING" ]; then
300 exit 1
301 fi
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600302 return
303 fi
304 fi
305
306 CONFIG="${ARGS[1]}"
307 FALLBACK="${ARGS[2]}"
308
309 if [ -n "${CONFIG}" ]; then
310 if [ -e "${CONFIG}" ]; then
311 LINTER_CONFIG+=( [${TITLE}]="${CONFIG}" )
312 elif [ -n "${FALLBACK}" ] && [ -e "${FALLBACK}" ]; then
313 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${CONFIG}; using ${FALLBACK}"
314 LINTER_CONFIG+=( [${TITLE}]="${FALLBACK}" )
315 else
316 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find config ${CONFIG}"
317 return
318 fi
319 fi
320
Patrick Williams816c7cc2022-12-08 05:51:57 -0600321 LINTERS_AVAILABLE+=( "${TITLE}" )
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600322}
323
Patrick Williams71b73242022-12-02 21:07:52 -0600324# Check for a global .linter-ignore file.
325GLOBAL_IGNORE=("cat")
326if [ -e ".linter-ignore" ]; then
327 GLOBAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter" ".linter-ignore")
328fi
329
330# Find all the files in the git repository and organize by type.
331declare -A FILES=()
332if [ -e .git/COMMIT_EDITMSG ]; then
333 FILES+=([commit]=".git/COMMIT_EDITMSG")
334fi
335while read -r file; do
336 ftype="$(get_file_type "$file")"
337 FILES+=([$ftype]="$(echo -ne "$file;${FILES[$ftype]:-}")")
Patrick Williamsf0584402023-07-11 11:27:33 -0500338done < <(git ls-files | xargs realpath --relative-base=. | "${GLOBAL_IGNORE[@]}")
Patrick Williams71b73242022-12-02 21:07:52 -0600339
340# For each linter, check if there are an applicable files and if it can
341# be enabled.
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600342for op in "${LINTERS_ALL[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600343 for ftype in ${LINTER_TYPES[$op]//;/ }; do
344 if [[ -v FILES["$ftype"] ]]; then
345 check_linter "$op" "${LINTER_REQUIRE[${op}]}"
346 break
347 fi
348 done
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600349done
350
Patrick Williams71b73242022-12-02 21:07:52 -0600351# Call each linter.
Patrick Williams816c7cc2022-12-08 05:51:57 -0600352for op in "${LINTERS_AVAILABLE[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600353
354 # Determine the linter-specific ignore file(s).
355 LOCAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter")
356 if [[ -v LINTER_IGNORE["$op"] ]]; then
357 for ignorefile in ${LINTER_IGNORE["$op"]//;/ } ; do
358 if [ -e "$ignorefile" ]; then
359 LOCAL_IGNORE+=("$ignorefile")
360 fi
361 done
362 fi
363 if [ 1 -eq ${#LOCAL_IGNORE[@]} ]; then
364 LOCAL_IGNORE=("cat")
365 fi
366
367 # Find all the files for this linter, filtering out the ignores.
368 LINTER_FILES=()
369 while read -r file ; do
370 if [ -e "$file" ]; then
371 LINTER_FILES+=("$file")
372 fi
373 done < <(for ftype in ${LINTER_TYPES[$op]//;/ }; do
374 # shellcheck disable=SC2001
375 echo "${FILES["$ftype"]:-}" | sed "s/;/\\n/g"
376 done | "${LOCAL_IGNORE[@]}")
377
378 # Call the linter now with all the files.
Patrick Williams200ec182022-12-06 08:32:03 -0600379 if [ 0 -ne ${#LINTER_FILES[@]} ]; then
380 echo -e " ${BLUE}Running $op${NORMAL}"
381 if ! "do_$op" "${LINTER_FILES[@]}" ; then
382 LINTERS_FAILED+=([$op]=1)
383 fi
384 else
385 echo -e " ${YELLOW}${op}:${NORMAL} all applicable files are on ignore-lists"
Patrick Williams785327a2022-12-05 16:20:43 -0600386 fi
Patrick Williams38515a32022-11-27 06:58:56 -0600387done
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030388
Patrick Williams785327a2022-12-05 16:20:43 -0600389# Check for failing linters.
390if [ 0 -ne ${#LINTERS_FAILED[@]} ]; then
391 for op in "${!LINTERS_FAILED[@]}"; do
392 echo -e "$op: ${RED}FAILED${NORMAL}"
393 done
394 exit 1
395fi
396
Patrick Williams71b73242022-12-02 21:07:52 -0600397# Check for differences.
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600398if [ -z "$OPTION_NO_DIFF" ]; then
399 echo -e " ${BLUE}Result differences...${NORMAL}"
400 if ! git --no-pager diff --exit-code ; then
401 echo -e "Format: ${RED}FAILED${NORMAL}"
402 exit 1
403 else
404 echo -e "Format: ${GREEN}PASSED${NORMAL}"
405 fi
Patrick Williamsc1513c82022-11-27 17:37:08 -0600406fi
407
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030408# Sometimes your situation is terrible enough that you need the flexibility.
409# For example, phosphor-mboxd.
Patrick Williams330b0772022-11-28 06:14:06 -0600410for formatter in "format-code.sh" "format-code"; do
411 if [[ -x "${formatter}" ]]; then
412 echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}"
413 "./${formatter}"
414 if [ -z "$OPTION_NO_DIFF" ]; then
415 git --no-pager diff --exit-code
416 fi
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600417 fi
Patrick Williams330b0772022-11-28 06:14:06 -0600418done