blob: 83fbcd52afcc076bca79e43647a268cf095a97bb [file] [log] [blame]
#!/bin/bash
set -e
# This script reformats source files using various formatters and linters.
#
# Files are changed in-place, so make sure you don't have anything open in an
# editor, and you may want to commit before formatting in case of awryness.
#
# This must be run on a clean repository to succeed
#
function display_help()
{
echo "usage: format-code.sh [-h | --help] [--no-diff]"
echo " [<path>]"
echo
echo "Format and lint a repository."
echo
echo "Arguments:"
echo " --no-diff Don't show final diff output"
echo " path Path to git repository (default to pwd)"
}
eval set -- "$(getopt -o 'h' --long 'help,no-diff' -n 'format-code.sh' -- "$@")"
while true; do
case "$1" in
'-h'|'--help')
display_help && exit 0
;;
'--no-diff')
OPTION_NO_DIFF=1
shift
;;
'--')
shift
break
;;
*)
echo "unknown option: $1"
display_help && exit 1
;;
esac
done
# Detect tty and set nicer colors.
if [ -t 1 ]; then
BLUE="\e[34m"
GREEN="\e[32m"
NORMAL="\e[0m"
RED="\e[31m"
YELLOW="\e[33m"
else # non-tty, no escapes.
BLUE=""
GREEN=""
NORMAL=""
RED=""
YELLOW=""
fi
# Allow called scripts to know which clang format we are using
export CLANG_FORMAT="clang-format"
# Path to default config files for linters.
CONFIG_PATH="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/config"
# Find repository root for `pwd` or $1.
if [ -z "$1" ]; then
DIR="$(git rev-parse --show-toplevel || pwd)"
else
DIR="$(git -C "$1" rev-parse --show-toplevel)"
fi
if [ ! -e "$DIR/.git" ]; then
echo -e "${RED}Error:${NORMAL} Directory ($DIR) does not appear to be a git repository"
exit 1
fi
cd "${DIR}"
echo -e " ${BLUE}Formatting code under${NORMAL} $DIR"
LINTERS_ALL=( \
commit_gitlint \
commit_spelling \
clang_format \
eslint \
pycodestyle \
shellcheck \
)
declare -A LINTER_REQUIRE=()
declare -A LINTER_CONFIG=()
LINTERS_ENABLED=()
LINTER_REQUIRE+=([commit_spelling]="codespell")
function do_commit_spelling() {
if [ ! -e .git/COMMIT_EDITMSG ]; then
return
fi
echo -e " ${BLUE}Running codespell${NORMAL}"
# Run the codespell with openbmc spcific spellings on the patchset
echo "openbmc-dictionary - misspelling count >> "
sed "s/Signed-off-by.*//" .git/COMMIT_EDITMSG | \
codespell -D "${CONFIG_PATH}/openbmc-spelling.txt" -d --count -
# Run the codespell with generic dictionary on the patchset
echo "generic-dictionary - misspelling count >> "
sed "s/Signed-off-by.*//" .git/COMMIT_EDITMSG | \
codespell --builtin clear,rare,en-GB_to_en-US -d --count -
}
LINTER_REQUIRE+=([commit_gitlint]="gitlint")
function do_commit_gitlint() {
echo -e " ${BLUE}Running gitlint${NORMAL}"
# Check for commit message issues
gitlint \
--extra-path "${CONFIG_PATH}/gitlint/" \
--config "${CONFIG_PATH}/.gitlint"
}
LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json")
function do_eslint() {
echo -e " ${BLUE}Running eslint${NORMAL}"
if [[ -f ".eslintignore" ]]; then
ESLINT_IGNORE="--ignore-path=.eslintignore"
elif [[ -f ".gitignore" ]]; then
ESLINT_IGNORE="--ignore-path=.gitignore"
fi
eslint . "${ESLINT_IGNORE}" --no-eslintrc -c "${LINTER_CONFIG[eslint]}" \
--ext .json --format=stylish \
--resolve-plugins-relative-to /usr/local/lib/node_modules \
--no-error-on-unmatched-pattern
}
LINTER_REQUIRE+=([pycodestyle]="pycodestyle;setup.cfg")
function do_pycodestyle() {
echo -e " ${BLUE}Running pycodestyle${NORMAL}"
pycodestyle --show-source --exclude=subprojects .
rc=$?
if [[ ${rc} -ne 0 ]]; then
exit ${rc}
fi
}
LINTER_REQUIRE+=([shellcheck]="shellcheck;.shellcheck")
function do_shellcheck() {
# Run shellcheck on any shell-script.
shell_scripts="$(git ls-files | xargs -n1 file -0 | \
grep -a "shell script" | cut -d '' -f 1)"
if [ -n "${shell_scripts}" ]; then
echo -e " ${BLUE}Running shellcheck${NORMAL}"
fi
for script in ${shell_scripts}; do
shellcheck --color=never -x "${script}"
done
}
LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format")
do_clang_format() {
echo -e " ${BLUE}Running clang-format${NORMAL}"
files=$(git ls-files | \
grep -e '\.[ch]pp$' -e '\.[ch]$' | \
grep -v '\.mako\.')
if [ -e .clang-ignore ]; then
files=$("${CONFIG_PATH}/lib/ignore-filter" .clang-ignore <<< "${files}")
fi
xargs "${CLANG_FORMAT}" -i <<< "${files}"
}
function check_linter()
{
TITLE="$1"
IFS=";" read -r -a ARGS <<< "$2"
EXE="${ARGS[0]}"
if [ ! -x "${EXE}" ]; then
if ! which "${EXE}" > /dev/null 2>&1 ; then
echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${EXE}"
return
fi
fi
CONFIG="${ARGS[1]}"
FALLBACK="${ARGS[2]}"
if [ -n "${CONFIG}" ]; then
if [ -e "${CONFIG}" ]; then
LINTER_CONFIG+=( [${TITLE}]="${CONFIG}" )
elif [ -n "${FALLBACK}" ] && [ -e "${FALLBACK}" ]; then
echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find ${CONFIG}; using ${FALLBACK}"
LINTER_CONFIG+=( [${TITLE}]="${FALLBACK}" )
else
echo -e " ${YELLOW}${TITLE}:${NORMAL} cannot find config ${CONFIG}"
return
fi
fi
LINTERS_ENABLED+=( "${TITLE}" )
}
for op in "${LINTERS_ALL[@]}"; do
check_linter "$op" "${LINTER_REQUIRE[${op}]}"
done
for op in "${LINTERS_ENABLED[@]}"; do
"do_$op"
done
if [ -z "$OPTION_NO_DIFF" ]; then
echo -e " ${BLUE}Result differences...${NORMAL}"
if ! git --no-pager diff --exit-code ; then
echo -e "Format: ${RED}FAILED${NORMAL}"
exit 1
else
echo -e "Format: ${GREEN}PASSED${NORMAL}"
fi
fi
# Sometimes your situation is terrible enough that you need the flexibility.
# For example, phosphor-mboxd.
for formatter in "format-code.sh" "format-code"; do
if [[ -x "${formatter}" ]]; then
echo -e " ${BLUE}Calling secondary formatter:${NORMAL} ${formatter}"
"./${formatter}"
if [ -z "$OPTION_NO_DIFF" ]; then
git --no-pager diff --exit-code
fi
fi
done