blob: 64ffcb19353709409f10826ba0c4a1c9ee4b5b8d [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"
Patrick Williams26d9d2c2022-12-05 15:03:07 -060022 echo " --allow-missing Run even if linters are not all present"
Patrick Williams147f37a2022-12-04 14:57:10 -060023 echo " path Path to git repository (default to pwd)"
Patrick Williamsc1513c82022-11-27 17:37:08 -060024}
Andrew Geissler9b0105c2018-01-10 10:58:35 -080025
Patrick Williams147f37a2022-12-04 14:57:10 -060026LINTERS_ALL=( \
27 commit_gitlint \
28 commit_spelling \
29 clang_format \
30 eslint \
31 pycodestyle \
32 shellcheck \
33 )
34LINTERS_DISABLED=()
35LINTERS_ENABLED=()
36
Patrick Williams26d9d2c2022-12-05 15:03:07 -060037eval set -- "$(getopt -o 'h' --long 'help,list-tools,no-diff,disable:,allow-missing' -n 'format-code.sh' -- "$@")"
Patrick Williamsc1513c82022-11-27 17:37:08 -060038while true; do
39 case "$1" in
40 '-h'|'--help')
41 display_help && exit 0
42 ;;
Andrew Geissler9b0105c2018-01-10 10:58:35 -080043
Patrick Williams147f37a2022-12-04 14:57:10 -060044 '--list-tools')
45 echo "Available tools:"
46 for t in "${LINTERS_ALL[@]}"; do
47 echo " $t"
48 done
49 exit 0
50 ;;
51
Patrick Williamsd27ab4c2022-11-27 17:51:35 -060052 '--no-diff')
53 OPTION_NO_DIFF=1
54 shift
55 ;;
56
Patrick Williams147f37a2022-12-04 14:57:10 -060057 '--disable')
58 LINTERS_DISABLED+=("$2")
59 shift && shift
60 ;;
61
Patrick Williams26d9d2c2022-12-05 15:03:07 -060062 '--allow-missing')
63 ALLOW_MISSING=yes
64 shift
65 ;;
66
Patrick Williamsc1513c82022-11-27 17:37:08 -060067 '--')
68 shift
69 break
70 ;;
71
72 *)
73 echo "unknown option: $1"
74 display_help && exit 1
75 ;;
76 esac
77done
78
79# Detect tty and set nicer colors.
80if [ -t 1 ]; then
81 BLUE="\e[34m"
82 GREEN="\e[32m"
83 NORMAL="\e[0m"
84 RED="\e[31m"
85 YELLOW="\e[33m"
86else # non-tty, no escapes.
87 BLUE=""
88 GREEN=""
89 NORMAL=""
90 RED=""
91 YELLOW=""
92fi
93
Patrick Williamsdad9b7a2022-11-30 13:10:20 -060094# Allow called scripts to know which clang format we are using
95export CLANG_FORMAT="clang-format"
96
Patrick Williamsc1513c82022-11-27 17:37:08 -060097# Path to default config files for linters.
98CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config"
99
100# Find repository root for `pwd` or $1.
101if [ -z "$1" ]; then
102 DIR="$(git rev-parse --show-toplevel || pwd)"
103else
104 DIR="$(git -C "$1" rev-parse --show-toplevel)"
105fi
Patrick Williamsbc85b712022-12-05 15:16:27 -0600106if [ ! -e "$DIR/.git" ]; then
107 echo -e "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600108 exit 1
109fi
Andrew Jeffery0cce8482018-04-30 11:37:01 +0930110
Manojkiran Edae6f120a2021-03-20 11:13:43 +0530111cd "${DIR}"
Patrick Williamsc1513c82022-11-27 17:37:08 -0600112echo -e " ${BLUE}Formatting code under${NORMAL} $DIR"
Andrew Geissler9b0105c2018-01-10 10:58:35 -0800113
Patrick Williams3d294942022-12-05 14:58:15 -0600114# Config hashes:
115# LINTER_REQUIRE - The requirements to run a linter, semi-colon separated.
116# 1. Executable.
117# 2. [optional] Configuration file.
118# 3. [optional] Global fallback configuration file.
119#
120# LINTER_IGNORE - An optional set of semi-colon separated ignore-files
121# specific to the linter.
122#
123# LINTER_TYPES - The file types supported by the linter, semi-colon separated.
124#
125# LINTER_CONFIG - The config (from LINTER_REQUIRE) chosen for the repository.
126#
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600127declare -A LINTER_REQUIRE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600128declare -A LINTER_IGNORE=()
Patrick Williams71b73242022-12-02 21:07:52 -0600129declare -A LINTER_TYPES=()
Patrick Williams3d294942022-12-05 14:58:15 -0600130declare -A LINTER_CONFIG=()
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530131
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600132LINTER_REQUIRE+=([commit_spelling]="codespell")
Patrick Williams71b73242022-12-02 21:07:52 -0600133LINTER_TYPES+=([commit_spelling]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600134function do_commit_spelling() {
Patrick Williams38515a32022-11-27 06:58:56 -0600135 # Run the codespell with openbmc spcific spellings on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600136 echo -n "openbmc-dictionary - misspelling count >> "
137 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600138 codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count -
Patrick Williams38515a32022-11-27 06:58:56 -0600139
140 # Run the codespell with generic dictionary on the patchset
Patrick Williams71b73242022-12-02 21:07:52 -0600141 echo -n "generic-dictionary - misspelling count >> "
142 sed "s/Signed-off-by.*//" "$@" | \
Patrick Williams38515a32022-11-27 06:58:56 -0600143 codespell --builtin clear,rare,en-GB_to_en-US -d --count -
144}
145
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600146LINTER_REQUIRE+=([commit_gitlint]="gitlint")
Patrick Williams71b73242022-12-02 21:07:52 -0600147LINTER_TYPES+=([commit_gitlint]="commit")
Patrick Williams38515a32022-11-27 06:58:56 -0600148function do_commit_gitlint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600149 gitlint --extra-path "${CONFIG_PATH}/gitlint/" \
Patrick Williamsc1513c82022-11-27 17:37:08 -0600150 --config "${CONFIG_PATH}/.gitlint"
Patrick Williams38515a32022-11-27 06:58:56 -0600151}
152
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600153LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json")
Patrick Williams71b73242022-12-02 21:07:52 -0600154LINTER_IGNORE+=([eslint]=".eslintignore")
155LINTER_TYPES+=([eslint]="json")
Patrick Williams38515a32022-11-27 06:58:56 -0600156function do_eslint() {
Patrick Williams71b73242022-12-02 21:07:52 -0600157 eslint --no-eslintrc -c "${LINTER_CONFIG[eslint]}" \
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600158 --ext .json --format=stylish \
159 --resolve-plugins-relative-to /usr/local/lib/node_modules \
Patrick Williams71b73242022-12-02 21:07:52 -0600160 --no-error-on-unmatched-pattern "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600161}
Manojkiran Eda87111bb2021-08-14 11:26:16 +0530162
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600163LINTER_REQUIRE+=([pycodestyle]="pycodestyle;setup.cfg")
Patrick Williams71b73242022-12-02 21:07:52 -0600164LINTER_TYPES+=([pycodestyle]="python")
Patrick Williams38515a32022-11-27 06:58:56 -0600165function do_pycodestyle() {
Patrick Williams71b73242022-12-02 21:07:52 -0600166 pycodestyle --show-source "$@"
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+=([shellcheck]="shellcheck;.shellcheck")
Patrick Williams71b73242022-12-02 21:07:52 -0600170LINTER_TYPES+=([shellcheck]="bash;sh")
Patrick Williams38515a32022-11-27 06:58:56 -0600171function do_shellcheck() {
Patrick Williams71b73242022-12-02 21:07:52 -0600172 shellcheck --color=never -x "$@"
Patrick Williams38515a32022-11-27 06:58:56 -0600173}
James Feistf1665d62019-11-22 09:06:33 -0800174
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600175LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format")
Patrick Williams71b73242022-12-02 21:07:52 -0600176LINTER_IGNORE+=([clang_format]=".clang-ignore;.clang-format-ignore")
177LINTER_TYPES+=([clang_format]="c;cpp")
Patrick Williams38515a32022-11-27 06:58:56 -0600178do_clang_format() {
Patrick Williams71b73242022-12-02 21:07:52 -0600179 "${CLANG_FORMAT}" -i "$@"
180}
Patrick Williams38515a32022-11-27 06:58:56 -0600181
Patrick Williams71b73242022-12-02 21:07:52 -0600182function get_file_type()
183{
184 case "$(basename "$1")" in
185 # First to early detect template files.
186 *.in | *.meson) echo "meson-template" && return ;;
187 *.mako | *.mako.*) echo "mako" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600188
Patrick Williams71b73242022-12-02 21:07:52 -0600189 *.ac) echo "autoconf" && return ;;
190 *.[ch]) echo "c" && return ;;
191 *.[ch]pp) echo "cpp" && return ;;
192 *.json) echo "json" && return ;;
193 *.md) echo "markdown" && return ;;
194 *.py) echo "python" && return ;;
195 *.yaml | *.yml) echo "yaml" && return ;;
Patrick Williamsd535ecb2022-12-02 14:54:18 -0600196
Patrick Williams71b73242022-12-02 21:07:52 -0600197 # Special files.
198 .git/COMMIT_EDITMSG) echo "commit" && return ;;
199 meson.build) echo "meson" && return ;;
200 esac
201
202 case "$(file "$1")" in
203 *Bourne-Again\ shell*) echo "bash" && return ;;
204 *C++\ source*) echo "cpp" && return ;;
205 *C\ source*) echo "c" && return ;;
206 *JSON\ data*) echo "json" && return ;;
207 *POSIX\ shell*) echo "sh" && return ;;
208 *Python\ script*) echo "python" && return ;;
209 *zsh\ shell*) echo "zsh" && return ;;
210 esac
211
212 echo "unknown"
Patrick Williams38515a32022-11-27 06:58:56 -0600213}
214
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600215function check_linter()
216{
217 TITLE="$1"
218 IFS=";" read -r -a ARGS <<< "$2"
219
Patrick Williams147f37a2022-12-04 14:57:10 -0600220 if [[ "${LINTERS_DISABLED[*]}" =~ $1 ]]; then
221 return
222 fi
223
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600224 EXE="${ARGS[0]}"
225 if [ ! -x "${EXE}" ]; then
226 if ! which "${EXE}" > /dev/null 2>&1 ; then
227 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${EXE}"
Patrick Williams26d9d2c2022-12-05 15:03:07 -0600228 if [ -z "$ALLOW_MISSING" ]; then
229 exit 1
230 fi
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600231 return
232 fi
233 fi
234
235 CONFIG="${ARGS[1]}"
236 FALLBACK="${ARGS[2]}"
237
238 if [ -n "${CONFIG}" ]; then
239 if [ -e "${CONFIG}" ]; then
240 LINTER_CONFIG+=( [${TITLE}]="${CONFIG}" )
241 elif [ -n "${FALLBACK}" ] && [ -e "${FALLBACK}" ]; then
242 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${CONFIG}; using ${FALLBACK}"
243 LINTER_CONFIG+=( [${TITLE}]="${FALLBACK}" )
244 else
245 echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find config ${CONFIG}"
246 return
247 fi
248 fi
249
250 LINTERS_ENABLED+=( "${TITLE}" )
251}
252
Patrick Williams71b73242022-12-02 21:07:52 -0600253# Check for a global .linter-ignore file.
254GLOBAL_IGNORE=("cat")
255if [ -e ".linter-ignore" ]; then
256 GLOBAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter" ".linter-ignore")
257fi
258
259# Find all the files in the git repository and organize by type.
260declare -A FILES=()
261if [ -e .git/COMMIT_EDITMSG ]; then
262 FILES+=([commit]=".git/COMMIT_EDITMSG")
263fi
264while read -r file; do
265 ftype="$(get_file_type "$file")"
266 FILES+=([$ftype]="$(echo -ne "$file;${FILES[$ftype]:-}")")
267done < <(git ls-files | "${GLOBAL_IGNORE[@]}")
268
269# For each linter, check if there are an applicable files and if it can
270# be enabled.
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600271for op in "${LINTERS_ALL[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600272 for ftype in ${LINTER_TYPES[$op]//;/ }; do
273 if [[ -v FILES["$ftype"] ]]; then
274 check_linter "$op" "${LINTER_REQUIRE[${op}]}"
275 break
276 fi
277 done
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600278done
279
Patrick Williams71b73242022-12-02 21:07:52 -0600280# Call each linter.
Patrick Williamsdad9b7a2022-11-30 13:10:20 -0600281for op in "${LINTERS_ENABLED[@]}"; do
Patrick Williams71b73242022-12-02 21:07:52 -0600282
283 # Determine the linter-specific ignore file(s).
284 LOCAL_IGNORE=("${CONFIG_PATH}/lib/ignore-filter")
285 if [[ -v LINTER_IGNORE["$op"] ]]; then
286 for ignorefile in ${LINTER_IGNORE["$op"]//;/ } ; do
287 if [ -e "$ignorefile" ]; then
288 LOCAL_IGNORE+=("$ignorefile")
289 fi
290 done
291 fi
292 if [ 1 -eq ${#LOCAL_IGNORE[@]} ]; then
293 LOCAL_IGNORE=("cat")
294 fi
295
296 # Find all the files for this linter, filtering out the ignores.
297 LINTER_FILES=()
298 while read -r file ; do
299 if [ -e "$file" ]; then
300 LINTER_FILES+=("$file")
301 fi
302 done < <(for ftype in ${LINTER_TYPES[$op]//;/ }; do
303 # shellcheck disable=SC2001
304 echo "${FILES["$ftype"]:-}" | sed "s/;/\\n/g"
305 done | "${LOCAL_IGNORE[@]}")
306
307 # Call the linter now with all the files.
308 echo -e " ${BLUE}Running $op${NORMAL}"
309 "do_$op" "${LINTER_FILES[@]}"
Patrick Williams38515a32022-11-27 06:58:56 -0600310done
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030311
Patrick Williams71b73242022-12-02 21:07:52 -0600312# Check for differences.
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600313if [ -z "$OPTION_NO_DIFF" ]; then
314 echo -e " ${BLUE}Result differences...${NORMAL}"
315 if ! git --no-pager diff --exit-code ; then
316 echo -e "Format: ${RED}FAILED${NORMAL}"
317 exit 1
318 else
319 echo -e "Format: ${GREEN}PASSED${NORMAL}"
320 fi
Patrick Williamsc1513c82022-11-27 17:37:08 -0600321fi
322
Andrew Jeffery457b6d12018-03-09 15:28:14 +1030323# Sometimes your situation is terrible enough that you need the flexibility.
324# For example, phosphor-mboxd.
Patrick Williams330b0772022-11-28 06:14:06 -0600325for formatter in "format-code.sh" "format-code"; do
326 if [[ -x "${formatter}" ]]; then
327 echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}"
328 "./${formatter}"
329 if [ -z "$OPTION_NO_DIFF" ]; then
330 git --no-pager diff --exit-code
331 fi
Patrick Williamsd27ab4c2022-11-27 17:51:35 -0600332 fi
Patrick Williams330b0772022-11-28 06:14:06 -0600333done