blob: d6e3a2dff694c3a4c9c361211a639b25df29919f [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 Williams147f37a2022-12-04 14:57:10 -060013 echo "usage: format-code.sh [-h | --help] [--no-diff] [--disable <tool>]"
14 echo " [--list-tools] [<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"
22 echo " path Path to git repository (default to pwd)"
Patrick Williamsc1513c82022-11-27 17:37:08 -060023}
Andrew Geissler9b0105c2018-01-10 10:58:35 -080024
Patrick Williams147f37a2022-12-04 14:57:10 -060025LINTERS_ALL=( \
26 commit_gitlint \
27 commit_spelling \
28 clang_format \
29 eslint \
30 pycodestyle \
31 shellcheck \
32 )
33LINTERS_DISABLED=()
34LINTERS_ENABLED=()
35
36eval set -- "$(getopt -o 'h' --long 'help,list-tools,no-diff,disable:' -n 'format-code.sh' -- "$@")"
Patrick Williamsc1513c82022-11-27 17:37:08 -060037while true; do
38 case "$1" in
39 '-h'|'--help')
40 display_help && exit 0
41 ;;
Andrew Geissler9b0105c2018-01-10 10:58:35 -080042
Patrick Williams147f37a2022-12-04 14:57:10 -060043 '--list-tools')
44 echo "Available tools:"
45 for t in "${LINTERS_ALL[@]}"; do
46 echo " $t"
47 done
48 exit 0
49 ;;
50
Patrick Williamsd27ab4c2022-11-27 17:51:35 -060051 '--no-diff')
52 OPTION_NO_DIFF=1
53 shift
54 ;;
55
Patrick Williams147f37a2022-12-04 14:57:10 -060056 '--disable')
57 LINTERS_DISABLED+=("$2")
58 shift && shift
59 ;;
60
Patrick Williamsc1513c82022-11-27 17:37:08 -060061 '--')
62 shift
63 break
64 ;;
65
66 *)
67 echo "unknown option: $1"
68 display_help && exit 1
69 ;;
70 esac
71done
72
73# Detect tty and set nicer colors.
74if [ -t 1 ]; then
75 BLUE="\e[34m"
76 GREEN="\e[32m"
77 NORMAL="\e[0m"
78 RED="\e[31m"
79 YELLOW="\e[33m"
80else # non-tty, no escapes.
81 BLUE=""
82 GREEN=""
83 NORMAL=""
84 RED=""
85 YELLOW=""
86fi
87
Patrick Williamsdad9b7a2022-11-30 13:10:20 -060088# Allow called scripts to know which clang format we are using
89export CLANG_FORMAT="clang-format"
90
Patrick Williamsc1513c82022-11-27 17:37:08 -060091# Path to default config files for linters.
92CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config"
93
94# Find repository root for `pwd` or $1.
95if [ -z "$1" ]; then
96 DIR="$(git rev-parse --show-toplevel || pwd)"
97else
98 DIR="$(git -C "$1" rev-parse --show-toplevel)"
99fi
Patrick Williamsbc85b712022-12-05 15:16:27 -0600100if [ ! -e "$DIR/.git" ]; then
101 echo -e "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600102 exit 1
103fi
Andrew Jeffery0cce8482018-04-30 11:37:01 +0930104
Manojkiran Edae6f120a2021-03-20 11:13:43 +0530105cd "${DIR}"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600106echo -e " ${BLUE}Formatting code under${NORMAL} $DIR"
Andrew Geissler9b0105c2018-01-10 10:58:35 -0800107
Patrick Williams3d294942022-12-05 14:58:15 -0600108# Config hashes:
109# LINTER_REQUIRE - The requirements to run a linter, semi-colon separated.
110# 1. Executable.
111# 2. [optional] Configuration file.
112# 3. [optional] Global fallback configuration file.
113#
114# LINTER_IGNORE - An optional set of semi-colon separated ignore-files
115# specific to the linter.
116#
117# LINTER_TYPES - The file types supported by the linter, semi-colon separated.
118#
119# LINTER_CONFIG - The config (from LINTER_REQUIRE) chosen for the repository.
120#
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600121declare -A LINTER_REQUIRE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600122declare -A LINTER_IGNORE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600123declare -A LINTER_TYPES=()
Patrick Williams3d294942022-12-05 14:58:15 -0600124declare -A LINTER_CONFIG=()
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530125
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600126LINTER_REQUIRE+=([commit_spelling]="codespell")
Patrick Williams71b73242022-12-02 21:07:52 -0600127LINTER_TYPES+=([commit_spelling]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600128function do_commit_spelling() {
Patrick Williams38515a32022-11-27 06:58:56 -0600129 # Run the codespell with openbmc spcific spellings on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600130 echo -n "openbmc-dictionary - misspelling count >> "
131 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600132 codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count -
Patrick Williams38515a32022-11-27 06:58:56 -0600133
134 # Run the codespell with generic dictionary on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600135 echo -n "generic-dictionary - misspelling count >> "
136 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williams38515a32022-11-27 06:58:56 -0600137 codespell --builtin clear,rare,en-GB_to_en-US -d --count -
138}
139
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600140LINTER_REQUIRE+=([commit_gitlint]="gitlint")
Patrick Williams71b73242022-12-02 21:07:52 -0600141LINTER_TYPES+=([commit_gitlint]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600142function do_commit_gitlint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600143 gitlint --extra-path "${CONFIG_PATH}/gitlint/" \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600144 --config "${CONFIG_PATH}/.gitlint"
Patrick Williams38515a32022-11-27 06:58:56 -0600145}
146
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600147LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json")
Patrick Williams71b73242022-12-02 21:07:52 -0600148LINTER_IGNORE+=([eslint]=".eslintignore")
149LINTER_TYPES+=([eslint]="json")
Patrick Williams38515a32022-11-27 06:58:56 -0600150function do_eslint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600151 eslint --no-eslintrc -c "${LINTER_CONFIG[eslint]}" \
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600152 --ext .json --format=stylish \
153 --resolve-plugins-relative-to /usr/local/lib/node_modules \
Patrick Williams71b73242022-12-02 21:07:52 -0600154 --no-error-on-unmatched-pattern "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600155}
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530156
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600157LINTER_REQUIRE+=([pycodestyle]="pycodestyle;setup.cfg")
Patrick Williams71b73242022-12-02 21:07:52 -0600158LINTER_TYPES+=([pycodestyle]="python")
Patrick Williams38515a32022-11-27 06:58:56 -0600159function do_pycodestyle() {
Patrick Williams71b73242022-12-02 21:07:52 -0600160 pycodestyle --show-source "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600161}
James Feistf1665d62019-11-22 09:06:33 -0800162
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600163LINTER_REQUIRE+=([shellcheck]="shellcheck;.shellcheck")
Patrick Williams71b73242022-12-02 21:07:52 -0600164LINTER_TYPES+=([shellcheck]="bash;sh")
Patrick Williams38515a32022-11-27 06:58:56 -0600165function do_shellcheck() {
Patrick Williams71b73242022-12-02 21:07:52 -0600166 shellcheck --color=never -x "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600167}
James Feistf1665d62019-11-22 09:06:33 -0800168
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600169LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format")
Patrick Williams71b73242022-12-02 21:07:52 -0600170LINTER_IGNORE+=([clang_format]=".clang-ignore;.clang-format-ignore")
171LINTER_TYPES+=([clang_format]="c;cpp")
Patrick Williams38515a32022-11-27 06:58:56 -0600172do_clang_format() {
Patrick Williams71b73242022-12-02 21:07:52 -0600173 "${CLANG_FORMAT}" -i "$@"
174}
Patrick Williams38515a32022-11-27 06:58:56 -0600175
Patrick Williams71b73242022-12-02 21:07:52 -0600176function get_file_type()
177{
178 case "$(basename "$1")" in
179 # First to early detect template files.
180 *.in | *.meson) echo "meson-template" && return ;;
181 *.mako | *.mako.*) echo "mako" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600182
Patrick Williams71b73242022-12-02 21:07:52 -0600183 *.ac) echo "autoconf" && return ;;
184 *.[ch]) echo "c" && return ;;
185 *.[ch]pp) echo "cpp" && return ;;
186 *.json) echo "json" && return ;;
187 *.md) echo "markdown" && return ;;
188 *.py) echo "python" && return ;;
189 *.yaml | *.yml) echo "yaml" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600190
Patrick Williams71b73242022-12-02 21:07:52 -0600191 # Special files.
192 .git/COMMIT_EDITMSG) echo "commit" && return ;;
193 meson.build) echo "meson" && return ;;
194 esac
195
196 case "$(file "$1")" in
197 *Bourne-Again\ shell*) echo "bash" && return ;;
198 *C++\ source*) echo "cpp" && return ;;
199 *C\ source*) echo "c" && return ;;
200 *JSON\ data*) echo "json" && return ;;
201 *POSIX\ shell*) echo "sh" && return ;;
202 *Python\ script*) echo "python" && return ;;
203 *zsh\ shell*) echo "zsh" && return ;;
204 esac
205
206 echo "unknown"
Patrick Williams38515a32022-11-27 06:58:56 -0600207}
208
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600209function check_linter()
210{
211 TITLE="$1"
212 IFS=";" read -r -a ARGS <<< "$2"
213
Patrick Williams147f37a2022-12-04 14:57:10 -0600214 if [[ "${LINTERS_DISABLED[*]}" =~ $1 ]]; then
215 return
216 fi
217
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600218 EXE="${ARGS[0]}"
219 if [ ! -x "${EXE}" ]; then
220 if ! which "${EXE}" > /dev/null 2>&1 ; then
221 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${EXE}"
222 return
223 fi
224 fi
225
226 CONFIG="${ARGS[1]}"
227 FALLBACK="${ARGS[2]}"
228
229 if [ -n "${CONFIG}" ]; then
230 if [ -e "${CONFIG}" ]; then
231 LINTER_CONFIG+=( [${TITLE}]="${CONFIG}" )
232 elif [ -n "${FALLBACK}" ] && [ -e "${FALLBACK}" ]; then
233 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${CONFIG}; using ${FALLBACK}"
234 LINTER_CONFIG+=( [${TITLE}]="${FALLBACK}" )
235 else
236 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find config ${CONFIG}"
237 return
238 fi
239 fi
240
241 LINTERS_ENABLED+=( "${TITLE}" )
242}
243
Patrick Williams71b73242022-12-02 21:07:52 -0600244# Check for a global .linter-ignore file.
245GLOBAL_IGNORE=("cat")
246if [ -e ".linter-ignore" ]; then
247 GLOBAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter" ".linter-ignore")
248fi
249
250# Find all the files in the git repository and organize by type.
251declare -A FILES=()
252if [ -e .git/COMMIT_EDITMSG ]; then
253 FILES+=([commit]=".git/COMMIT_EDITMSG")
254fi
255while read -r file; do
256 ftype="$(get_file_type "$file")"
257 FILES+=([$ftype]="$(echo -ne "$file;${FILES[$ftype]:-}")")
258done < <(git ls-files | "${GLOBAL_IGNORE[@]}")
259
260# For each linter, check if there are an applicable files and if it can
261# be enabled.
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600262for op in "${LINTERS_ALL[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600263 for ftype in ${LINTER_TYPES[$op]//;/ }; do
264 if [[ -v FILES["$ftype"] ]]; then
265 check_linter "$op" "${LINTER_REQUIRE[${op}]}"
266 break
267 fi
268 done
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600269done
270
Patrick Williams71b73242022-12-02 21:07:52 -0600271# Call each linter.
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600272for op in "${LINTERS_ENABLED[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600273
274 # Determine the linter-specific ignore file(s).
275 LOCAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter")
276 if [[ -v LINTER_IGNORE["$op"] ]]; then
277 for ignorefile in ${LINTER_IGNORE["$op"]//;/ } ; do
278 if [ -e "$ignorefile" ]; then
279 LOCAL_IGNORE+=("$ignorefile")
280 fi
281 done
282 fi
283 if [ 1 -eq ${#LOCAL_IGNORE[@]} ]; then
284 LOCAL_IGNORE=("cat")
285 fi
286
287 # Find all the files for this linter, filtering out the ignores.
288 LINTER_FILES=()
289 while read -r file ; do
290 if [ -e "$file" ]; then
291 LINTER_FILES+=("$file")
292 fi
293 done < <(for ftype in ${LINTER_TYPES[$op]//;/ }; do
294 # shellcheck disable=SC2001
295 echo "${FILES["$ftype"]:-}" | sed "s/;/\\n/g"
296 done | "${LOCAL_IGNORE[@]}")
297
298 # Call the linter now with all the files.
299 echo -e " ${BLUE}Running $op${NORMAL}"
300 "do_$op" "${LINTER_FILES[@]}"
Patrick Williams38515a32022-11-27 06:58:56 -0600301done
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030302
Patrick Williams71b73242022-12-02 21:07:52 -0600303# Check for differences.
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600304if [ -z "$OPTION_NO_DIFF" ]; then
305 echo -e " ${BLUE}Result differences...${NORMAL}"
306 if ! git --no-pager diff --exit-code ; then
307 echo -e "Format: ${RED}FAILED${NORMAL}"
308 exit 1
309 else
310 echo -e "Format: ${GREEN}PASSED${NORMAL}"
311 fi
Patrick Williamsc1513c82022-11-27 17:37:08 -0600312fi
313
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030314# Sometimes your situation is terrible enough that you need the flexibility.
315# For example, phosphor-mboxd.
Patrick Williams330b0772022-11-28 06:14:06 -0600316for formatter in "format-code.sh" "format-code"; do
317 if [[ -x "${formatter}" ]]; then
318 echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}"
319 "./${formatter}"
320 if [ -z "$OPTION_NO_DIFF" ]; then
321 git --no-pager diff --exit-code
322 fi
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600323 fi
Patrick Williams330b0772022-11-28 06:14:06 -0600324done