Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Exclude files and folders from linting (closes #35) #38

Merged
merged 6 commits into from
Nov 7, 2021
Merged
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ A GitHub Action that performs static analysis for shell scripts using [ShellChec

# Usage

Shell Linter can perform static analysis in various ways. You can use it to lint all the shell scripts in your project or lint a specific file or folder using the `path` parameter. Specific use cases are shown below:
Shell Linter can perform static analysis in various ways. By default it scans all the Shellcheck-supported shell scripts (sh/bash/dash/ksh) in your project. However, you can use the `path` parameter to scan a specific file or folder or use the `exclude-paths` parameter to exclude files or folders from the scan. With Shell Linter, you can also specify the minimum severity of errors to consider using the `severity` parameter. Specific use cases along with examples are shown below:

Run static analysis for all shell scripts in your repository:
#### Run static analysis for all the supported shell scripts in your repository:
```yml
jobs:
lint:
Expand All @@ -27,48 +27,57 @@ jobs:
uses: azohra/shell-linter@latest
```

Run static analysis for a single shell script:
#### Run static analysis for a single shell script:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@latest
with:
path: "setup.sh"
```

Run static analysis for multiple shell scripts **with or without** extension:
#### Run static analysis for multiple shell scripts **with or without** extension:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@latest
with:
path: "setup,deploy.sh"
```

Run static analysis for all the shell scripts in a folder:
#### Run static analysis for all the shell scripts in a folder:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@latest
with:
path: "src"
```

Run static analysis using a **wildcard** path:
#### Run static analysis using a **wildcard** path:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@latest
with:
path: "src/*.sh"
```
#### Exclude files and folders from the static analysis:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@latest
with:
exclude-paths: "src/setup.sh,tests/unit_tests"
```
Note that `exclude-paths` only accepts paths relative to your project's root directory. However, **do not** include `./` at the beginning of the paths.

Run static analysis for all the shell scripts and only report issue with error severity:
To exclude a folder and it's content recursively just provide the path of the folder **without** a `/` at the end. In the example above, the entire folder at the path `tests/unit_tests` will be excluded from linting.

#### Run static analysis for all the shell scripts and only report issue with error severity:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@latest
with:
path: "src/*.sh"
severity: "error"
```

Run analysis by using a specific version of Shell Linter:
#### Run analysis by using a specific version of Shell Linter:
```yml
- name: Run Shellcheck
uses: azohra/shell-linter@v0.5.0
Expand All @@ -77,11 +86,12 @@ Run analysis by using a specific version of Shell Linter:
# Input

### `path`

Optional. Execute lint check on a specific file or folder. Default: `.`

### `severity`
### `exclude-paths`
Optional. Exclude files and folders from Shellcheck scan.

### `severity`
Optional. Specify minimum severity of errors to consider [style, info, warning, error]. Default: `style`

# License
Expand Down
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ inputs:
description: 'Specify minimum severity of errors to consider.'
required: false
default: 'style'
exclude-paths:
description: 'Specify files or folders to exclude during scan.'
required: false
default: ''

runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.path }}
- ${{ inputs.severity }}
- ${{ inputs.exclude-paths}}
branding:
icon: 'check-circle'
color: 'green'
Expand Down
31 changes: 22 additions & 9 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
# shellcheck disable=SC2155

input_paths="$1"
severity_mode="${2}"
execution_mode="$3"
severity_mode="$2"
exclude_paths="$3"
execution_mode="$4"
my_dir=$(pwd)
status_code="0"
find_path_clauses=(! -path "${my_dir}/.git/*")
invalid_files=()
scan_regex="#!.*[/ ](sh|bash|dash|ksh)$"

Expand All @@ -19,18 +21,28 @@ process_input(){
severity_mode="style"
fi

if [ "$input_paths" != "." ]; then
if [ -n "$exclude_paths" ]; then
for path in $(echo "$exclude_paths" | tr "," "\n"); do
if [ -d "${my_dir}/$path" ]; then
find_path_clauses+=( ! -path "${my_dir}/$path/*")
else
find_path_clauses+=( ! -path "${my_dir}/$path" )
fi
done
fi

if [[ -n "$input_paths" && "$input_paths" != "." ]]; then
for path in $(echo "$input_paths" | tr "," "\n"); do
if [ -d "$path" ]; then
scan_all "$path"
scan_dir "$path"
else
scan_file "$path"
fi
done
[[ ${#invalid_files[@]} -gt 0 ]] && log_invalid_files
[ -z "$execution_mode" ] && exit $status_code
else
scan_all "$my_dir"
scan_dir "$my_dir"
[[ ${#invalid_files[@]} -gt 0 ]] && log_invalid_files
[ -z "$execution_mode" ] && exit $status_code
fi
Expand Down Expand Up @@ -59,7 +71,7 @@ scan_file(){
fi
}

scan_all(){
scan_dir(){
echo "Scanning all the shell scripts at $1 🔎"
while IFS= read -r script
do
Expand All @@ -69,7 +81,7 @@ scan_all(){
else
invalid_files+=( $script )
fi
done < <(find "$1" -iname '*.sh' -o -iname '*.bash' -o -iname '*.ksh' -o ! -iname '*.*' -type f ! -path "$1/.git/*")
done < <(find "$1" -type f \( -iname '*.sh' -o -iname '*.bash' -o -iname '*.ksh' -o ! -iname '*.*' \) "${find_path_clauses[@]}")
}

# Logging files with no extension that are not amongst the supported scripts or scripts that are supported but don't have a shebang.
Expand All @@ -78,8 +90,9 @@ log_invalid_files(){
for file in ${invalid_files[@]}; do
printf "\n\t\e[33m %s \e[0m\n" "$file"
done
printf "\n\e[33m ShellCheck only supports sh/bash/dash/ksh scripts. For supported scripts to be scanned, make sure to add a proper shebang on the first line of the script.\e[0m\n"
printf "\n\e[33m ShellCheck only supports sh/bash/dash/ksh scripts. For supported scripts to be scanned, make sure to add a proper shebang on the first line of the script.\n\n To fix the warning for the unsupported scripts or to ignore specific files, use the 'exclude-paths' input. For more information check:
https://github.com/azohra/shell-linter#input\e[0m\n"
}

# To avoid execution when sourcing this script for testing
[ "$0" = "${BASH_SOURCE[0]}" ] && process_input "$@"
[ "$0" = "${BASH_SOURCE[0]}" ] && process_input
47 changes: 47 additions & 0 deletions tests/integration_tests/ignored_path_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#! /bin/bash
# shellcheck disable=SC2155

source ./entrypoint.sh "" "" "" "--test"

test_ignore_directories(){
local exclude_paths="test_dir,severity_mode"
local actual_message=$(process_input)
local message1="Scanning example_script.sh"
local message2="Scanning test_script_info.sh"
local expected1="Scanning test_script_wsh.sh"

assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$message1"
assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$message2"
assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected1"
}

test_ignore_files(){
local input_paths="./test_data/script_type"
local exclude_paths="script_type/test.zsh,script_type/test_script.js,script_type/test_zsh_wsh,script_type/test_python,script_type/test_script_wosh.sh"
local actual_message=$(process_input)
local expected1="Scanning sample.bash"
local expected2="Scanning test_script_wsh.sh"
local notExpected="ShellCheck only supports sh/bash/dash/ksh scripts. For supported scripts to be scanned, make sure to add a proper shebang on the first line of the script."

assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected1"
assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected2"
assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$notExpected"
}

test_ignore_file_and_directory(){
local exclude_paths="script_type,severity_mode,test_dir/invalid_script"
local actual_message=$(process_input)
local expected1="Scanning example_script.sh"
local expected2="Scanning executable_script"
local notExpected="ShellCheck only supports sh/bash/dash/ksh scripts. For supported scripts to be scanned, make sure to add a proper shebang on the first line of the script."

assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected1"
assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected2"
assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$notExpected"
}

tearDown(){
input_paths=""
invalid_files=()
}
source ./tests/shunit2
8 changes: 2 additions & 6 deletions tests/integration_tests/input_path_tests.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#! /bin/bash
# shellcheck disable=SC2155

source ./entrypoint.sh "" "" "--test"
source ./entrypoint.sh "" "" "" "--test"

test_execution_mode(){
local expected_path=./test_data
Expand All @@ -19,7 +19,6 @@ test_invalid_script_with_extension(){

assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1"
assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2"

}

test_invalid_script_without_extension(){
Expand All @@ -30,7 +29,6 @@ test_invalid_script_without_extension(){

assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1"
assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2"

}

test_unsupported_script_without_extension(){
Expand Down Expand Up @@ -61,7 +59,6 @@ test_valid_file_without_shebang(){

assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1"
assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2"

}

test_valid_file_input(){
Expand Down Expand Up @@ -114,9 +111,8 @@ test_input_files_with_wildcard() {
assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2"
}



tearDown(){
input_paths=""
invalid_files=()
}
source ./tests/shunit2
2 changes: 1 addition & 1 deletion tests/integration_tests/severity_mode_tests.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /bin/bash

source ./entrypoint.sh "" "" "--test"
source ./entrypoint.sh "" "" "" "--test"

test_severity_mode_invalid(){
input_paths="./test_data/severity_mode/test_script_warning.sh"
Expand Down
29 changes: 15 additions & 14 deletions tests/unit_tests/scan_tests.sh
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
#! /bin/bash
# shellcheck disable=SC2155

source ./entrypoint.sh "" "style" "--test"
source ./entrypoint.sh "" "style" "" "--test"

# scan_file() tests
# scan_file tests
test_scan_valid_script_with_extension(){
local expected="Scanning sample.bash"
local actual=$(scan_file ./test_data/script_type/sample.bash)

assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected"
}

test_scan_valid_script_without_extension(){
local expected="Scanning executable_script"
local actual=$(scan_file ./test_data/test_dir/executable_script)

assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected"
}

test_scan_unsuppoted_script(){
test_scan_unsupported_script(){
local expected1="Scanning test.zsh"
local expected2="ShellCheck only supports sh/bash/dash/ksh scripts. For supported scripts to be scanned, make sure to add a proper shebang on the first line of the script."
local actual=$(scan_file ./test_data/script_type/test.zsh)
assertNotContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1"
assertNotContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2"

assertNotContains "Actual messages:$actual contains the message.\n" "$actual" "$expected1"
assertNotContains "Actual messages:$actual contains the message.\n" "$actual" "$expected2"
}

test_scan_external_sourced_file(){
Expand All @@ -30,31 +33,29 @@ test_scan_external_sourced_file(){
local expected="Scanning external_sources.sh"

assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected"
assertNotContains "Actual messages:$actual\n contains the unexpected message: '$notExpected'\n" "$actual" "$notExpected"

assertNotContains "Actual messages:$actual\n contains the unexpected message: '$notExpected'\n" "$actual" "$notExpected"
}

# scan_all() tests
# scan_dir tests
test_scan_a_directory(){
local message1="Scanning all the shell scripts at ./test_data/script_type"
local message2="Scanning sample.bash"
local message3="Scanning test_script_wsh.sh"
local message4="ShellCheck only supports sh/bash/dash/ksh scripts. For supported scripts to be scanned, make sure to add a proper shebang on the first line of the script."
local actual_message=$(scan_all ./test_data/script_type)
local actual_message=$(scan_dir ./test_data/script_type)

assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$message1"
assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$message2"
assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$message3"
assertNotContains "Actual messages:$actual_message contain the expected message.\n" "$actual_message" "$message4"

assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$message4"
}

test_unscanned_files_count(){
local expected_count=3
scan_all ./test_data/script_type > /dev/null
scan_dir ./test_data/script_type > /dev/null
local actual_count="${#invalid_files[@]}"
assertEquals "$expected_count" "$actual_count"

assertEquals "$expected_count" "$actual_count"
}

source ./tests/shunit2