diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee3f48b..8c3ad48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Tests +name: CI on: ['push', 'pull_request'] @@ -10,6 +10,20 @@ defaults: working-directory: './' jobs: + shellcheck: + name: ShellCheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install prerequisites + run: | + sudo apt-get update -y + sudo apt-get install -y --no-install-recommends shellcheck + - name: Run ShellCheck + run: | + shellcheck --version + bin/shellcheck + test-macos: name: MacOS Test strategy: diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..6443957 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,6 @@ +source-path=lib +source-path=tests +source-path=tests/playground + +source-path=./tests/* +disable=SC2164,SC2103 # cd dir || exit 1 / cd .. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c013b3 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.PHONY: lint +lint: + ./bin/shellcheck $(filter-out $@,$(MAKECMDGOALS)) + +.PHONY: help +help: + @echo 'lint - run linters and code format checkers' diff --git a/_test b/_test index f290d02..c46aa4c 100755 --- a/_test +++ b/_test @@ -25,7 +25,7 @@ all_tests_passed=true run_single_test() { echo "== Running $test_file" - $shell_command $test_file 2>> "$stderr_file" || all_tests_passed=false + $shell_command "$test_file" 2>> "$stderr_file" || all_tests_passed=false echo "" } @@ -34,7 +34,7 @@ run_all_tests() { for file in "${_test_files[@]}" ; do echo "== Running $file" - $shell_command $file 2>> "$stderr_file" || all_tests_passed=false + $shell_command "$file" 2>> "$stderr_file" || all_tests_passed=false echo "" done diff --git a/bin/shellcheck b/bin/shellcheck new file mode 100755 index 0000000..5f284b9 --- /dev/null +++ b/bin/shellcheck @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -e + +if [[ $1 == --list ]]; then + list_only_the_targets=true + shift +fi + +if [[ -n $1 ]]; then + targets=("$@") +else + targets=() + while IFS= read -r -d $'\0'; do + targets+=("$REPLY") + done < <( + # Find files + # - That are not in vendor or .git directories + # - That are shell scripts or have no extension and are bash executables + find . -type f -not -path './vendor/*' -not -path './.git/*' \( -name '*.sh' -o \ + \( -not -name '*.*' -exec sh -c 'head -n 1 "$1" | grep -q "^#!/usr/bin/env bash"' _ {} \; \) \) -print0 + ) +fi + +# List all targets +if [[ $list_only_the_targets ]]; then + printf "%s\n" "${targets[@]}" + exit 0 +fi + +shellcheck --external-sources --shell=bash "${targets[@]}" + +exit $? diff --git a/dev_tools.sh b/dev_tools.sh index c8a9b3f..68b55f4 100644 --- a/dev_tools.sh +++ b/dev_tools.sh @@ -10,5 +10,5 @@ timer() { shift fi - echo "$(time ( $@ ) 2>&1 1>/dev/null )"s + time ( "$@" ) > /dev/null } diff --git a/lib/init.sh b/lib/init.sh index d27b74e..e7d0a24 100644 --- a/lib/init.sh +++ b/lib/init.sh @@ -1,5 +1,3 @@ -#!/usr/bin/env zsh - export SHERPA_ENABLED=true export SHERPA_LOG_LEVEL='info' # debug, info, no talking @@ -11,6 +9,7 @@ fi SHERPA_PATH="$(dirname "$SHERPA_LIB_PATH")" +# shellcheck disable=SC2034 SHERPA_CHECKSUM_DIR="$HOME/.local/share/local_sherpa" # Load the dependencies @@ -18,10 +17,10 @@ source "$SHERPA_PATH/vendor/smartcd/arrays" source "$SHERPA_PATH/vendor/smartcd/varstash" # Load the app -source $SHERPA_LIB_PATH/logger.sh -source $SHERPA_LIB_PATH/trust_verification.sh -source $SHERPA_LIB_PATH/local_env_file_parser.sh -source $SHERPA_LIB_PATH/setup_cd_hook.sh +source "$SHERPA_LIB_PATH/logger.sh" +source "$SHERPA_LIB_PATH/trust_verification.sh" +source "$SHERPA_LIB_PATH/local_env_file_parser.sh" +source "$SHERPA_LIB_PATH/setup_cd_hook.sh" source "$SHERPA_LIB_PATH/sherpa.sh" diff --git a/lib/local_env_file_parser.sh b/lib/local_env_file_parser.sh index f06cfa4..b220771 100644 --- a/lib/local_env_file_parser.sh +++ b/lib/local_env_file_parser.sh @@ -9,8 +9,8 @@ _fetch_variable_names() { local filter_pattern='^[[:space:]]*export[[:space:]]+[[:alnum:]_]+' # Cleanup: # export var_1 -> var_1 - local variable_names=$(grep -oE "$filter_pattern" .local-sherpa | \ - awk '{print $2}') + local variable_names + variable_names=$(grep -oE "$filter_pattern" .local-sherpa | awk '{print $2}') if [ -n "$variable_names" ]; then echo "$variable_names" @@ -18,11 +18,12 @@ _fetch_variable_names() { } _fetch_aliase_names() { - local filter_pattern='^[[:space:]]*alias[[:space:]]' + local filter_pattern + filter_pattern='^[[:space:]]*alias[[:space:]]' # Cleanup: # alias alias_1=... -> alias_1 - local alias_names=$(grep -E "$filter_pattern" .local-sherpa | \ - awk -F'[ =]' '{print $2}') + local alias_names + alias_names=$(grep -E "$filter_pattern" .local-sherpa | awk -F'[ =]' '{print $2}') if [ -n "$alias_names" ]; then echo "$alias_names" @@ -30,13 +31,15 @@ _fetch_aliase_names() { } _fetch_function_names() { - local filter_pattern='^[[:space:]]*([[:alnum:]_]+[[:space:]]*\(\)|function[[:space:]]+[[:alnum:]_]+)' + local filter_pattern + filter_pattern='^[[:space:]]*([[:alnum:]_]+[[:space:]]*\(\)|function[[:space:]]+[[:alnum:]_]+)' # Cleanup: # function_1() -> function_1 # function function_2() -> function_2() -> function_2 - local function_names=$(grep -oE "$filter_pattern" .local-sherpa | \ - sed 's/function //' | \ - sed 's/()//') + local function_names + function_names=$(grep -oE "$filter_pattern" .local-sherpa | \ + sed 's/function //' | \ + sed 's/()//') if [ -n "$function_names" ]; then echo "$function_names" diff --git a/lib/setup_cd_hook.sh b/lib/setup_cd_hook.sh index 7a1a698..a939267 100644 --- a/lib/setup_cd_hook.sh +++ b/lib/setup_cd_hook.sh @@ -1,9 +1,10 @@ setup_cd_hook() { if [ -n "$ZSH_VERSION" ]; then # ZSH - function sherpa_chpwd_handler() { + # shellcheck disable=SC2317 + sherpa_chpwd_handler() { # Changed directory? - if [[ -n $OLDPWD && $PWD != $OLDPWD ]]; then + if [[ -n $OLDPWD && $PWD != "$OLDPWD" ]]; then alert_sherpa_we_changed_dir fi } @@ -12,6 +13,7 @@ setup_cd_hook() { add-zsh-hook chpwd sherpa_chpwd_handler else # BASH + # shellcheck disable=SC2317 _sherpa_chpwd_hook() { # run commands in CHPWD_COMMAND variable on dir change if [[ "$PREVPWD" != "$PWD" ]]; then diff --git a/lib/sherpa.sh b/lib/sherpa.sh index c5b1829..76152ff 100644 --- a/lib/sherpa.sh +++ b/lib/sherpa.sh @@ -1,4 +1,4 @@ -function sherpa() { +sherpa() { local command="$1" local usage_text="Example usage: @@ -21,7 +21,7 @@ Tell sherpa how much he should talk (works only for the current session): e|edit|init) edit; trust_current_env; unload_current_env; load_current_env;; rest|off|disable) disable;; work|on|enable) enable;; - talk) shift; set_log_level $1;; + talk) shift; set_log_level "$1";; debug) set_log_level "debug";; info) set_log_level "info";; shh|shhh) set_log_level "no talking";; @@ -117,10 +117,12 @@ load_current_env() { stash_local_env log_debug "Load local env" + # shellcheck disable=SC1091 source .local-sherpa # Append the current directory to the list. This is needed to unload the envs # in the right order when we change directories. The root directory should be # the last one to unload. + # shellcheck disable=SC2207 PATHS_WHERE_LOCAL_ENV_WAS_LOADED=($(pwd) "${PATHS_WHERE_LOCAL_ENV_WAS_LOADED[@]}") } @@ -136,6 +138,7 @@ was_env_loaded() { stash_local_env() { log_debug "Stash local env" + # shellcheck disable=SC2034 varstash_dir="$PWD" while IFS= read -r env_item_name || [[ -n $env_item_name ]]; do diff --git a/lib/trust_verification.sh b/lib/trust_verification.sh index b4fc263..cd886bc 100644 --- a/lib/trust_verification.sh +++ b/lib/trust_verification.sh @@ -5,9 +5,11 @@ _calculate_checksum() { } verify_trust() { - local checksum_file="$SHERPA_CHECKSUM_DIR/$(pwd | md5sum | cut -d ' ' -f 1)" + local checksum_file + checksum_file="$SHERPA_CHECKSUM_DIR/$(pwd | md5sum | cut -d ' ' -f 1)" - local current_checksum=$(_calculate_checksum) + local current_checksum + current_checksum=$(_calculate_checksum) # No checksum file? if [[ ! -f "$checksum_file" ]]; then @@ -15,7 +17,8 @@ verify_trust() { return 1 fi - local stored_checksum=$(cat "$checksum_file") + local stored_checksum + stored_checksum=$(cat "$checksum_file") # Did the local env file change? if [[ "$current_checksum" != "$stored_checksum" ]]; then @@ -35,8 +38,10 @@ trust_current_env() { mkdir -p "$SHERPA_CHECKSUM_DIR" - local checksum_file="$SHERPA_CHECKSUM_DIR/$(pwd | md5sum | cut -d ' ' -f 1)" - local current_checksum=$(_calculate_checksum) + local checksum_file + checksum_file="$SHERPA_CHECKSUM_DIR/$(pwd | md5sum | cut -d ' ' -f 1)" + local current_checksum + current_checksum=$(_calculate_checksum) echo "$current_checksum" > "$checksum_file" log_info "Trusted!" @@ -45,7 +50,8 @@ trust_current_env() { } untrust_current_env() { - local checksum_file="$SHERPA_CHECKSUM_DIR/$(pwd | md5sum | cut -d ' ' -f 1)" + local checksum_file + checksum_file="$SHERPA_CHECKSUM_DIR/$(pwd | md5sum | cut -d ' ' -f 1)" if [[ -f "$checksum_file" ]]; then rm "$checksum_file" diff --git a/test_all b/test_all index 20fce49..66cc376 100755 --- a/test_all +++ b/test_all @@ -2,11 +2,11 @@ _tests_passed=true -./test_bash $@ || _tests_passed=false +./test_bash "$@" || _tests_passed=false echo "" echo "" -./test_zsh $@ || _tests_passed=false +./test_zsh "$@" || _tests_passed=false echo "" diff --git a/test_all_in_ubuntu b/test_all_in_ubuntu index 4346078..a67e815 100755 --- a/test_all_in_ubuntu +++ b/test_all_in_ubuntu @@ -11,11 +11,11 @@ _tests_passed=true echo "**************** Running in Ubuntu ****************" -$_docker_exec ./test_bash $@ || _tests_passed=false +$_docker_exec ./test_bash "$@" || _tests_passed=false echo "" echo "" -$_docker_exec ./test_zsh $@ || _tests_passed=false +$_docker_exec ./test_zsh "$@" || _tests_passed=false echo "" diff --git a/test_bash b/test_bash index 0d86426..2342d26 100755 --- a/test_bash +++ b/test_bash @@ -3,4 +3,4 @@ # Disable history to avoid polluting the user's history file export HISTFILE= -./_test "bash" $@ +./_test "bash" "$@" diff --git a/test_zsh b/test_zsh index 59aa67d..ac78c76 100755 --- a/test_zsh +++ b/test_zsh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -./_test "zsh" $@ +./_test "zsh" "$@" diff --git a/tests/features/edit_test.sh b/tests/features/edit_test.sh index 1fd049e..a994cd9 100644 --- a/tests/features/edit_test.sh +++ b/tests/features/edit_test.sh @@ -3,6 +3,7 @@ source tests/support/init.sh # Setup cd playground/project_1 sherpa trust +# shellcheck disable=SC2154 is "$var_1" "LOCAL VAR PROJECT 1" "The local env is loaded" diff --git a/tests/features/loading_and_unloading_for_subfolders_test.sh b/tests/features/loading_and_unloading_for_subfolders_test.sh index 15d1413..74fe23e 100644 --- a/tests/features/loading_and_unloading_for_subfolders_test.sh +++ b/tests/features/loading_and_unloading_for_subfolders_test.sh @@ -30,6 +30,7 @@ is "$var_1" "LOCAL VAR SUBPROJECT" "Project 1 env is overridden by Subproject en is "$(alias_1)" "LOCAL ALIAS SUBPROJECT" "Project 1 env is overridden by Subproject env (alias_1)" is "$(function_1)" "LOCAL FUNCTION SUBPROJECT" "Project 1 env is overridden by Subproject env (function_1)" +# shellcheck disable=SC2154 is "$subvar_1" "SUBLOCAL VAR SUBPROJECT" "Subproject env is loaded (subvar_1)" is "$(subalias_1)" "SUBLOCAL ALIAS SUBPROJECT" "Subproject env is loaded (subalias_1)" is "$(subfunction_1)" "SUBLOCAL FUNCTION SUBPROJECT" "Subproject env is loaded (subfunction_1)" diff --git a/tests/support/assertions.sh b/tests/support/assertions.sh index 7cc4a91..6cb5470 100644 --- a/tests/support/assertions.sh +++ b/tests/support/assertions.sh @@ -20,7 +20,7 @@ is(){ if [ "$1" != "$2" ]; then _print_in_red "not ok" - [ -n "$message" ] && printf " - $message" + [ -n "$message" ] && printf " - %s" "$message" printf "\n\n" printf " failure: not an exact match\n\n" @@ -29,7 +29,7 @@ is(){ echo " got: $actual" echo "" - echo "$(_failed_assertion_path_with_line_number)" >&2 + _failed_assertion_path_with_line_number >&2 exit 1 else @@ -45,7 +45,7 @@ like(){ if [[ ! "$actual" =~ $expected_pattern ]]; then _print_in_red "not ok" - [ -n "$message" ] && printf " - $message" + [ -n "$message" ] && printf " - %s" "$message" printf "\n\n" printf " failure: not a partial match\n\n" @@ -54,7 +54,7 @@ like(){ echo " got: $actual" echo "" - echo "$(_failed_assertion_path_with_line_number)" >&2 + _failed_assertion_path_with_line_number >&2 exit 1 else @@ -69,13 +69,13 @@ is_undefined(){ if _is_defined "$item"; then _print_in_red "not ok" - [ -n "$message" ] && printf " - $message" + [ -n "$message" ] && printf " - %s" "$message" printf "\n\n" - printf " failure: $item is defined when it should not be" + printf " failure: %s is defined when it should not be" "$item" echo "" - echo "$(_failed_assertion_path_with_line_number)" >&2 + _failed_assertion_path_with_line_number >&2 exit 1 else @@ -98,7 +98,7 @@ _is_defined() { } _failed_assertion_path_with_line_number(){ - if [ -n "$ZSH_VERSION" ]; then + if [ -n "$ZSH_VERSION" ]; then # shellcheck disable=SC2154 echo "${funcfiletrace[2]}" else echo "${BASH_SOURCE[2]}:${BASH_LINENO[1]}" @@ -110,10 +110,10 @@ _print_ok(){ local -r message="$1" printf "ok" - [ -n "$message" ] && printf " - $message" + [ -n "$message" ] && printf " - %s" "$message" printf "\n" } _print_in_red(){ - printf "\033[31m$1\033[0m" + printf "\033[31m%s\033[0m" "$1" } diff --git a/tests/support/init.sh b/tests/support/init.sh index 87b3b51..23b28f0 100644 --- a/tests/support/init.sh +++ b/tests/support/init.sh @@ -3,10 +3,11 @@ cd tests source ../lib/init.sh source support/assertions.sh +# shellcheck disable=SC2034 SHERPA_CHECKSUM_DIR="$SHERPA_PATH/tests/playground/local_sherpa_checksums" export SHERPA_LOG_LEVEL='no talk' # debug, info, no talking -if [ ! -n "$ZSH_VERSION" ]; then +if [ -z "$ZSH_VERSION" ]; then # Emulate the behavior of `cd` in interactive bash cd() { builtin cd "$@"