diff --git a/.gitignore b/.gitignore index a7adee70..d1c77707 100644 --- a/.gitignore +++ b/.gitignore @@ -89,7 +89,7 @@ test/reports # Test leftovers .lintr test/linters/rust_clippy/**/Cargo.lock -test/linters/rust_clippy/**/target/** +test/linters/rust_clippy/**/target # Super-linter ouputs super-linter-output/ diff --git a/Makefile b/Makefile index 7d688660..87a265a2 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ all: info docker test ## Run all targets. .PHONY: test -test: info validate-container-image-labels docker-build-check docker-dev-container-build-check test-lib inspec lint-codebase test-default-config-files test-actions-runner-debug test-actions-steps-debug test-runner-debug test-find lint-subset-files test-custom-ssl-cert test-non-default-workdir test-git-flags test-non-default-home-directory test-git-initial-commit test-git-merge-commit-push test-log-level test-use-find-and-ignore-gitignored-files test-linters-expect-failure-log-level-notice test-bash-exec-library-expect-success test-bash-exec-library-expect-failure test-save-super-linter-output test-save-super-linter-output-custom-path test-save-super-linter-custom-summary test-linters ## Run the test suite +test: info validate-container-image-labels docker-build-check docker-dev-container-build-check test-lib inspec lint-codebase fix-codebase test-default-config-files test-actions-runner-debug test-actions-steps-debug test-runner-debug test-find lint-subset-files test-custom-ssl-cert test-non-default-workdir test-git-flags test-non-default-home-directory test-git-initial-commit test-git-merge-commit-push test-log-level test-use-find-and-ignore-gitignored-files test-linters-expect-failure-log-level-notice test-bash-exec-library-expect-success test-bash-exec-library-expect-failure test-save-super-linter-output test-save-super-linter-output-custom-path test-save-super-linter-custom-summary test-linters test-linters-fix-mode-expect-success ## Run the test suite # if this session isn't interactive, then we don't want to allocate a # TTY, which would fail, but if it is interactive, we do want to attach @@ -231,6 +231,7 @@ test-git-flags: ## Run super-linter with different git-related flags .PHONY: lint-codebase lint-codebase: ## Lint the entire codebase docker run \ + -e CREATE_LOG_FILE=true \ -e RUN_LOCAL=true \ -e LOG_LEVEL=DEBUG \ -e DEFAULT_BRANCH=main \ @@ -238,11 +239,37 @@ lint-codebase: ## Lint the entire codebase -e FILTER_REGEX_EXCLUDE=".*(/test/linters/|CHANGELOG.md).*" \ -e GITLEAKS_CONFIG_FILE=".gitleaks-ignore-tests.toml" \ -e RENOVATE_SHAREABLE_CONFIG_PRESET_FILE_NAMES="default.json,hoge.json" \ + -e SAVE_SUPER_LINTER_OUTPUT=true \ -e SAVE_SUPER_LINTER_SUMMARY=true \ -e VALIDATE_ALL_CODEBASE=true \ -v "$(CURDIR):/tmp/lint" \ $(SUPER_LINTER_TEST_CONTAINER_URL) +# Return an error if there are changes to commit +.PHONY: fix-codebase +fix-codebase: ## Fix and format the entire codebase + docker run \ + -e CREATE_LOG_FILE=true \ + -e DEFAULT_BRANCH=main \ + -e ENABLE_GITHUB_ACTIONS_GROUP_TITLE=true \ + -e FILTER_REGEX_EXCLUDE=".*(/test/linters/|CHANGELOG.md).*" \ + -e FIX_ENV=true \ + -e FIX_JAVASCRIPT_ES=true \ + -e FIX_JAVASCRIPT_PRETTIER=true \ + -e FIX_JSON=true \ + -e FIX_MARKDOWN=true \ + -e FIX_SHELL_SHFMT=true \ + -e GITLEAKS_CONFIG_FILE=".gitleaks-ignore-tests.toml" \ + -e LOG_LEVEL=DEBUG \ + -e RUN_LOCAL=true \ + -e SAVE_SUPER_LINTER_OUTPUT=true \ + -e SAVE_SUPER_LINTER_SUMMARY=true \ + -e VALIDATE_ALL_CODEBASE=true \ + -v "$(CURDIR):/tmp/lint" \ + $(SUPER_LINTER_TEST_CONTAINER_URL) \ + && /bin/bash -c "source test/testUtils.sh; if ! CheckUnexpectedGitChanges ${CURDIR}; then exit 1; fi" + + # This is a smoke test to check how much time it takes to lint only a small # subset of files, compared to linting the whole codebase. .PHONY: lint-subset-files @@ -389,6 +416,14 @@ test-non-default-home-directory: ## Test a non-default HOME directory "run_test_cases_non_default_home" \ "$(IMAGE)" +.PHONY: test-linters-fix-mode-expect-success +test-linters-fix-mode-expect-success: ## Run the linters test suite (fix mode) expecting successes + $(CURDIR)/test/run-super-linter-tests.sh \ + $(SUPER_LINTER_TEST_CONTAINER_URL) \ + "run_test_case_fix_mode" \ + "$(IMAGE)" + + .PHONY: test-linters test-linters: test-linters-expect-success test-linters-expect-failure ## Run the linters test suite diff --git a/README.md b/README.md index 200d765f..0ac0ac9e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Super-Linter Super-linter is a ready-to-run collection of linters and code analyzers, to -help validate your source code. +help validate and fix your source code. The goal of super-linter is to help you establish best practices and consistent formatting across multiple programming languages, and ensure developers are @@ -12,6 +12,8 @@ issues that those tools find as console output, and as [GitHub Actions status checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks). You can also [run super-linter outside GitHub Actions](#run-super-linter-outside-github-actions). +Super-linter can also help you [fix linting and formatting issues](#fix-linting-and-formatting-issues). + Super-linter is licensed under an [MIT License](https://github.com/super-linter/super-linter/blob/main/LICENSE). @@ -183,9 +185,9 @@ Super-Linter provides several variants: - `pwsh` linters - `c#` linters -## Configure super-linter +## Configure Super-linter -You can configure super-linter using the following environment variables: +You can configure Super-linter using the following environment variables: | **Environment variable** | **Default Value** | **Description** | |-------------------------------------------------|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -207,6 +209,41 @@ You can configure super-linter using the following environment variables: | **ENABLE_GITHUB_ACTIONS_STEP_SUMMARY** | `false` if `RUN_LOCAL=true`, `true` otherwise | Flag to enable [GitHub Actions job summary](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary) for the Super-linter action. For more information, see [Summary outputs](#summary-outputs). | | **FILTER_REGEX_EXCLUDE** | not set | Regular expression defining which files will be excluded from linting (ex: `.*src/test.*`). Not setting this variable means to process all files. | | **FILTER_REGEX_INCLUDE** | not set | Regular expression defining which files will be processed by linters (ex: `.*src/.*`). Not setting this variable means to process all files. `FILTER_REGEX_INCLUDE` is evaluated before `FILTER_REGEX_EXCLUDE`. | +| **FIX_ANSIBLE** | `false` | Option to enable fix mode for `ANSIBLE`. | +| **FIX_CLANG_FORMAT** | `false` | Option to enable fix mode for `CLANG_FORMAT`. | +| **FIX_CSHARP** | `false` | Option to enable fix mode for `CSHARP`. | +| **FIX_CSS** | `false` | Option to enable fix mode for `CSS`. | +| **FIX_ENV** | `false` | Option to enable fix mode for `ENV`. | +| **FIX_GO** | `false` | Option to enable fix mode for `GO`. | +| **FIX_GO_MODULES** | `false` | Option to enable fix mode for `GO_MODULES`. | +| **FIX_GOOGLE_JAVA_FORMAT** | `false` | Option to enable fix mode for `GOOGLE_JAVA_FORMAT`. | +| **FIX_GROOVY** | `false` | Option to enable fix mode for `GROOVY`. | +| **FIX_JAVASCRIPT_ES** | `false` | Option to enable fix mode for `JAVASCRIPT_ES`. | +| **FIX_JAVASCRIPT_PRETTIER** | `false` | Option to enable fix mode for `JAVASCRIPT_PRETTIER`. | +| **FIX_JAVASCRIPT_STANDARD** | `false` | Option to enable fix mode for `JAVASCRIPT_STANDARD`. | +| **FIX_JSON** | `false` | Option to enable fix mode for `JSON`. | +| **FIX_JSONC** | `false` | Option to enable fix mode for `JSONC`. | +| **FIX_JSX** | `false` | Option to enable fix mode for `JSX`. | +| **FIX_MARKDOWN** | `false` | Option to enable fix mode for `MARKDOWN`. | +| **FIX_POWERSHELL** | `false` | Option to enable fix mode for `POWERSHELL`. | +| **FIX_PROTOBUF** | `false` | Option to enable fix mode for `PROTOBUF`. | +| **FIX_PYTHON_BLACK** | `false` | Option to enable fix mode for `PYTHON_BLACK`. | +| **FIX_PYTHON_ISORT** | `false` | Option to enable fix mode for `PYTHON_ISORT`. | +| **FIX_PYTHON_RUFF** | `false` | Option to enable fix mode for `PYTHON_RUFF`. | +| **FIX_RUBY** | `false` | Option to enable fix mode for `RUBY`. | +| **FIX_RUST_2015** | `false` | Option to enable fix mode for `RUST_2015`. | +| **FIX_RUST_2018** | `false` | Option to enable fix mode for `RUST_2018`. | +| **FIX_RUST_2021** | `false` | Option to enable fix mode for `RUST_2021`. | +| **FIX_RUST_CLIPPY** | `false` | Option to enable fix mode for `RUST_CLIPPY`. | +| **FIX_SCALAFMT** | `false` | Option to enable fix mode for `SCALAFMT`. | +| **FIX_SHELL_SHFMT** | `false` | Option to enable fix mode for `SHELL_SHFMT`. | +| **FIX_SNAKEMAKE_SNAKEFMT** | `false` | Option to enable fix mode for `SNAKEMAKE_SNAKEFMT`. | +| **FIX_SQLFLUFF** | `false` | Option to enable fix mode for `SQLFLUFF`. | +| **FIX_TERRAFORM_FMT** | `false` | Option to enable fix mode for `TERRAFORM_FMT`. | +| **FIX_TSX** | `false` | Option to enable fix mode for `TSX`. | +| **FIX_TYPESCRIPT_ES** | `false` | Option to enable fix mode for `TYPESCRIPT_ES`. | +| **FIX_TYPESCRIPT_PRETTIER** | `false` | Option to enable fix mode for `TYPESCRIPT_PRETTIER`. | +| **FIX_TYPESCRIPT_STANDARD** | `false` | Option to enable fix mode for `TYPESCRIPT_STANDARD`. | | **GITHUB_ACTIONS_CONFIG_FILE** | `actionlint.yml` | Filename for [Actionlint configuration](https://github.com/rhysd/actionlint/blob/main/docs/config.md) (ex: `actionlint.yml`) | | **GITHUB_ACTIONS_COMMAND_ARGS** | `null` | Additional arguments passed to `actionlint` command. Useful to [ignore some errors](https://github.com/rhysd/actionlint/blob/main/docs/usage.md#ignore-some-errors) | | **GITHUB_CUSTOM_API_URL** | `https://api.${GITHUB_DOMAIN}` | Specify a custom GitHub API URL in case GitHub Enterprise is used: e.g. `https://github.myenterprise.com/api/v3` | @@ -351,10 +388,39 @@ The `VALIDATE_[LANGUAGE]` variables work as follows: - If you set any of the `VALIDATE_[LANGUAGE]` variables to `false`, super-linter defaults to leaving any unset variable to true (only exclude those languages). - If you set any of the `VALIDATE_[LANGUAGE]` variables to both `true` and `false`, super-linter fails reporting an error. -For more information about reusing super-linter configuration across +For more information about reusing Super-linter configuration across environments, see [Share Environment variables between environments](docs/run-linter-locally.md#share-environment-variables-between-environments). +## Fix linting and formatting issues + +All the linters and formatters that Super-linter runs report errors if they +detect linting or formatting issues without modifying your source +code (_check only mode_). Check only mode is the default for all linters and +formatters that Super-linter runs. + +Certain linters and formatters support automatically fixing issues in your code +(_fix mode_). You can enable fix mode for a particular linter or formatter by +setting the relevant `FIX_` variable to `true`. To know which +linters and formatters support fix mode, refer to the +[Configure Super-linter section](#configure-super-linter). + +Setting a `FIX_` variable to `true` implies setting the +corresponding `VALIDATE_` to `true`. Setting a +`FIX_` variable to `true` and the corresponding +`VALIDATE_` to `false` is a configuration error. Super-linter +reports that as a fatal error. + +Super-linter supports the following locations to deliver fixes: + +- In the current Super-linter workspace, so you can process the changes to your + files by yourself. For example: + + - If you're running Super-linter in your CI environment, such as GitHub + Actions, you can commit and push changes as part of your workflow. + - If you're running Super-linter locally, you can commit the changes as you + would with any other change in your working directory. + ## Configure linters Super-linter provides default configurations for some linters in the [`TEMPLATES/`](TEMPLATES/) diff --git a/docs/add-new-linter.md b/docs/add-new-linter.md index 11d4d081..bfb8a3dc 100644 --- a/docs/add-new-linter.md +++ b/docs/add-new-linter.md @@ -8,8 +8,10 @@ new tool, it should include: - Provide test cases: 1. Create the `test/linters/` directory. - 2. Provide at least one test case with a file that is supposed to pass validation: `test/linters//-good` - 3. Provide at least one test case with a file that is supposed to fail validation: `test/linters//-bad` + 2. Provide at least one test case with a file that is supposed to pass validation, + with the right file extension if needed: `test/linters//-good` + 3. Provide at least one test case with a file that is supposed to fail validation, + with the right file extension if needed: `test/linters//-bad` - Update the test suite to check for installed packages, the commands that your new tool needs in the `PATH`, and the expected version command: @@ -89,28 +91,73 @@ new tool, it should include: - Update the orchestration scripts to run the new tool: - - `globals/languages.sh`: add a new item to `LANGUAGES_ARRAY` array. Use the - "name" of the language, then a `_`, and finally the name of the linter. Example: `PYTHON_RUFF` + - `lib/globals/languages.sh`: add a new item to `LANGUAGES_ARRAY` array. Use the + "name" of the language, then a `_`, and finally the name of the linter. Example: `PYTHON_RUFF`. + In the context of this document, to avoid repetitions we reference this new + item as ``. + - Linter configuration: - - `globals/linterRules.sh`: - - If the new linter accepts a configuration files from the command line, add a new variable - with a default filename using the item that you added to the `LANGUAGES_ARRAY` as a prefix, - followed by the `CONFIG_FILE` suffix. Example: - `PYTHON_RUFF_FILE_NAME="${PYTHON_RUFF_CONFIG_FILE:-.ruff.toml}"`. - - If there are arguments that you can only pass using the command line, and you think users - might want to customize them, define a new variable using the item that - you added to the `LANGUAGES_ARRAY` as a prefix, followed by the - `COMMAND_ARGS` suffix. Example: - `GITHUB_ACTIONS_COMMAND_ARGS="${GITHUB_ACTIONS_COMMAND_ARGS:-""}"` - Create a new minimal configuration file in the `TEMPLATES` directory with the same name as the default configuration filename. Example: `TEMPLATES/.ruff.toml`. - - `lib/linter.sh` - - `lib/functions/linterCommands.sh`: define a new array to invoke the new linter. - - Provide the logic to populate the list of files or directories to examine: `lib/buildFileList.sh` - - If necessary, provide elaborate logic to detect if the tool should examine a file or a directory: `lib/detectFiles.sh` - - If the tool needs to take into account special cases: + - `lib/globals/linterRules.sh`: + - If the new linter accepts a configuration files from the command line, + define a new variable: + `_FILE_NAME="${_CONFIG_FILE:-"default-config-file-name.conf"}"` + where `default-config-file-name.conf` is the name of the new, + minimal configuration for the linter. Example: + `PYTHON_RUFF_FILE_NAME="${PYTHON_RUFF_CONFIG_FILE:-.ruff.toml}"`. + - If there are arguments that you can only pass using the command line, and you think users + might want to customize them, define a new variable using + `_COMMAND_ARGS` and add it to the command if the + configuration provides it. Example: - - Provide new runtime validation checks in `lib/validation.sh`. - - Customize the logic to get the installed version of the tool: `scripts/linterVersions.sh` - - Provide custom logic to load configuration files: `lib/linterRules.sh` - - Provide custom logic for test cases and to run the tool: `lib/worker.sh` + ```bash + _COMMAND_ARGS="${_COMMAND_ARGS:-""}" + if [ -n "${_COMMAND_ARGS:-}" ]; then + export _COMMAND_ARGS + LINTER_COMMANDS_ARRAY_+=("${_COMMAND_ARGS}") + fi + ``` + + - Define the command to invoke the new linter: + + - `lib/functions/linterCommands.sh`: add the command to invoke the linter. + Define a new variable: `LINTER_COMMANDS_ARRAY_`. + Example: + `LINTER_COMMANDS_ARRAY_GO_MODULES=(golangci-lint run --allow-parallel-runners -c "${GO_LINTER_RULES}")` + + If the linter needs to load a configuration file, add the relevant options + and paths to the command you just defined. The path to the configuration + file is automatically initialized by Super-linter using in the + `_LINTER_RULES` variable, as in the `GO_LINTER_RULES` + example above for the `GO` language. + + - `lib/globals/linterCommandsOptions.sh`: add "check only mode" and "fix + linting and formatting issues mode" options if the linter supports it. + Super-linter will automatically add them to the command to run the linter. + + - If the linter runs in "fix linting and formatting issues mode" by + default, define a new variable with the options to add to the linter + command to enable "check only mode": + `_CHECK_ONLY_MODE_OPTIONS=(....)`. + Example: `PYTHON_BLACK_CHECK_ONLY_MODE_OPTIONS=(--diff --check)` + + - If the linter runs in "check only mode" by + default, define a new variable with the options to add to the linter + command to enable "fix linting and formatting issues mode": + `_FIX_MODE_OPTIONS=(...)`. + Example: `ANSIBLE_FIX_MODE_OPTIONS=(--fix)` + + - Provide the logic to populate the list of files or directories to examine: `lib/functions/buildFileList.sh` + - If necessary, provide elaborate logic to detect if the tool should examine a file or a directory: `lib/functions/detectFiles.sh` + - If the tool needs to take into account special cases, reach out to the + maintainers by creating a draft pull request and ask relevant questions + there. For example, you might need to provide new logic or customize + the existing one to: + + - Validate the runtime environment: `lib/functions/validation.sh`. + - Get the installed version of the linter: `scripts/linterVersions.sh` + - Load configuration files: `lib/functions/linterRules.sh` + - Run the linter: `lib/functions/worker.sh` + - Compose the linter command: `lib/functions/linterCommands.sh` + - Modify the core Super-linter logic: `lib/linter.sh` diff --git a/docs/run-linter-locally.md b/docs/run-linter-locally.md index 8818efc6..854f0a99 100644 --- a/docs/run-linter-locally.md +++ b/docs/run-linter-locally.md @@ -139,3 +139,12 @@ To get the list of the available `Make` targets, run the following command: ```shell make help ``` + +### Automatically fix formatting and linting issues + +To automatically fix linting and formatting issues when supported, run the +following command: + +```shell +make fix-codebase +``` diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 7e8bb7ff..b39bfca2 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -39,17 +39,14 @@ This section helps you upgrade from super-linter `v6.7.0` to `v6.8.0`. - If you set `JAVASCRIPT_DEFAULT_STYLE=standard`, set `VALIDATE_JAVASCRIPT_PRETTIER=false` - - If you set `TYPESCRIPT_DEFAULT_STYLE=standard`, set `VALIDATE_TYPESCRIPT_PRETTIER=false` - - If you set `JAVASCRIPT_DEFAULT_STYLE=prettier`, set `VALIDATE_JAVASCRIPT_STANDARD=false` - - If you set `TYPESCRIPT_DEFAULT_STYLE=prettier`, set `VALIDATE_TYPESCRIPT_STANDARD=false` - Finally, you remove both `JAVASCRIPT_DEFAULT_STYLE` and + Finally, remove both `JAVASCRIPT_DEFAULT_STYLE` and `TYPESCRIPT_DEFAULT_STYLE` from your Super-linter configuration. ## Upgrade from v5 to v6 diff --git a/lib/functions/linterCommands.sh b/lib/functions/linterCommands.sh index 64d73cfc..99e63b0c 100755 --- a/lib/functions/linterCommands.sh +++ b/lib/functions/linterCommands.sh @@ -1,17 +1,18 @@ #!/usr/bin/env bash +# shellcheck disable=SC2034 # Disable ununsed variables warning because we +# source this script and use these variables as globals + +# Load linter commands options so we don't need to load it every time we source +# source this file +# shellcheck source=/dev/null +source /action/lib/globals/linterCommandsOptions.sh + ########################## # Define linter commands # ########################## -# If there's no input argument, parallel adds a default {} at the end of the command. -# In a few cases, such as ANSIBLE and GO_MODULES, -# Consume the input before running the command because we need the input -# to set the working directory, but we don't need it appended at the end of the command. -# Setting -n 0 would not help in this case, because the input will not be passed -# to the --workdir option as well. -# shellcheck disable=SC2034 # Variable is referenced in other scripts -LINTER_COMMANDS_ARRAY_ANSIBLE=(ansible-lint -c "${ANSIBLE_LINTER_RULES}" "&& echo \"Linted: {}\"") +LINTER_COMMANDS_ARRAY_ANSIBLE=(ansible-lint -c "${ANSIBLE_LINTER_RULES}") LINTER_COMMANDS_ARRAY_ARM=(pwsh -NoProfile -NoLogo -Command "\"Import-Module ${ARM_TTK_PSD1} ; \\\${config} = \\\$(Import-PowerShellDataFile -Path ${ARM_LINTER_RULES}) ; Test-AzTemplate @config -TemplatePath '{}'; if (\\\${Error}.Count) { exit 1 }\"") LINTER_COMMANDS_ARRAY_BASH=(shellcheck --color --rcfile "${BASH_LINTER_RULES}") # This check and the BASH_SEVERITY variable are needed until Shellcheck supports @@ -35,12 +36,12 @@ else debug "Adding the '--directory' option to the Checkov command." LINTER_COMMANDS_ARRAY_CHECKOV+=(--directory) fi -LINTER_COMMANDS_ARRAY_CLANG_FORMAT=(clang-format --style=file:"${CLANG_FORMAT_LINTER_RULES}" --Werror --dry-run) +LINTER_COMMANDS_ARRAY_CLANG_FORMAT=(clang-format --style=file:"${CLANG_FORMAT_LINTER_RULES}" --Werror) LINTER_COMMANDS_ARRAY_CLOJURE=(clj-kondo --config "${CLOJURE_LINTER_RULES}" --lint) LINTER_COMMANDS_ARRAY_CLOUDFORMATION=(cfn-lint --config-file "${CLOUDFORMATION_LINTER_RULES}") LINTER_COMMANDS_ARRAY_COFFEESCRIPT=(coffeelint -f "${COFFEESCRIPT_LINTER_RULES}") LINTER_COMMANDS_ARRAY_CPP=(cpplint) -LINTER_COMMANDS_ARRAY_CSHARP=(dotnet format whitespace --folder --verify-no-changes --exclude / --include "{/}") +LINTER_COMMANDS_ARRAY_CSHARP=(dotnet format whitespace --folder --exclude / --include "{/}") LINTER_COMMANDS_ARRAY_CSS=(stylelint --config "${CSS_LINTER_RULES}") LINTER_COMMANDS_ARRAY_DART=(dart analyze --fatal-infos --fatal-warnings) LINTER_COMMANDS_ARRAY_DOCKERFILE_HADOLINT=(hadolint -c "${DOCKERFILE_HADOLINT_LINTER_RULES}") @@ -54,16 +55,15 @@ fi LINTER_COMMANDS_ARRAY_GITLEAKS=(gitleaks detect --no-banner --no-git --redact --config "${GITLEAKS_LINTER_RULES}" --verbose --source) LINTER_COMMANDS_ARRAY_GHERKIN=(gherkin-lint -c "${GHERKIN_LINTER_RULES}") LINTER_COMMANDS_ARRAY_GO=(golangci-lint run -c "${GO_LINTER_RULES}" --fast) -# Consume the input as we do with ANSIBLE -LINTER_COMMANDS_ARRAY_GO_MODULES=(golangci-lint run --allow-parallel-runners -c "${GO_LINTER_RULES}" "&& echo \"Linted: {}\"") +LINTER_COMMANDS_ARRAY_GO_MODULES=(golangci-lint run --allow-parallel-runners -c "${GO_LINTER_RULES}") LINTER_COMMANDS_ARRAY_GO_RELEASER=(goreleaser check) -LINTER_COMMANDS_ARRAY_GOOGLE_JAVA_FORMAT=(java -jar /usr/bin/google-java-format --dry-run --set-exit-if-changed) +LINTER_COMMANDS_ARRAY_GOOGLE_JAVA_FORMAT=(java -jar /usr/bin/google-java-format) LINTER_COMMANDS_ARRAY_GROOVY=(npm-groovy-lint -c "${GROOVY_LINTER_RULES}" --failon warning --no-insight) LINTER_COMMANDS_ARRAY_HTML=(htmlhint --config "${HTML_LINTER_RULES}") LINTER_COMMANDS_ARRAY_JAVA=(java -jar /usr/bin/checkstyle -c "${JAVA_LINTER_RULES}") LINTER_COMMANDS_ARRAY_JAVASCRIPT_ES=(eslint -c "${JAVASCRIPT_ES_LINTER_RULES}") +LINTER_COMMANDS_ARRAY_JAVASCRIPT_PRETTIER=(prettier) LINTER_COMMANDS_ARRAY_JAVASCRIPT_STANDARD=(standard "${JAVASCRIPT_STANDARD_LINTER_RULES}") -LINTER_COMMANDS_ARRAY_JAVASCRIPT_PRETTIER=(prettier --check) LINTER_COMMANDS_ARRAY_JSCPD=(jscpd --config "${JSCPD_LINTER_RULES}") JSCPD_GITIGNORE_OPTION="--gitignore" if [[ "${IGNORE_GITIGNORED_FILES}" == "true" ]]; then @@ -105,41 +105,105 @@ LINTER_COMMANDS_ARRAY_PHP_BUILTIN=(php -l -c "${PHP_BUILTIN_LINTER_RULES}") LINTER_COMMANDS_ARRAY_PHP_PHPCS=(phpcs --standard="${PHP_PHPCS_LINTER_RULES}") LINTER_COMMANDS_ARRAY_PHP_PHPSTAN=(phpstan analyse --no-progress --no-ansi --memory-limit 1G -c "${PHP_PHPSTAN_LINTER_RULES}") LINTER_COMMANDS_ARRAY_PHP_PSALM=(psalm --config="${PHP_PSALM_LINTER_RULES}") -LINTER_COMMANDS_ARRAY_POWERSHELL=(pwsh -NoProfile -NoLogo -Command "\"Invoke-ScriptAnalyzer -EnableExit -Settings ${POWERSHELL_LINTER_RULES} -Path '{}'; if (\\\${Error}.Count) { exit 1 }\"") -LINTER_COMMANDS_ARRAY_PROTOBUF=(protolint lint --config_path "${PROTOBUF_LINTER_RULES}") -LINTER_COMMANDS_ARRAY_PYTHON_BLACK=(black --config "${PYTHON_BLACK_LINTER_RULES}" --diff --check) +LINTER_COMMANDS_ARRAY_POWERSHELL=(Invoke-ScriptAnalyzer -EnableExit -Settings "${POWERSHELL_LINTER_RULES}" -Path '{}') +LINTER_COMMANDS_ARRAY_PROTOBUF=(protolint lint -config_path "${PROTOBUF_LINTER_RULES}") +LINTER_COMMANDS_ARRAY_PYTHON_BLACK=(black --config "${PYTHON_BLACK_LINTER_RULES}") LINTER_COMMANDS_ARRAY_PYTHON_PYLINT=(pylint --rcfile "${PYTHON_PYLINT_LINTER_RULES}") LINTER_COMMANDS_ARRAY_PYTHON_FLAKE8=(flake8 --config="${PYTHON_FLAKE8_LINTER_RULES}") -LINTER_COMMANDS_ARRAY_PYTHON_ISORT=(isort --check --diff --sp "${PYTHON_ISORT_LINTER_RULES}") +LINTER_COMMANDS_ARRAY_PYTHON_ISORT=(isort --sp "${PYTHON_ISORT_LINTER_RULES}") LINTER_COMMANDS_ARRAY_PYTHON_MYPY=(mypy --config-file "${PYTHON_MYPY_LINTER_RULES}" --install-types --non-interactive) LINTER_COMMANDS_ARRAY_PYTHON_RUFF=(ruff check --config "${PYTHON_RUFF_LINTER_RULES}") LINTER_COMMANDS_ARRAY_R=(R --slave -e "\"lints <- lintr::lint('{}');print(lints);errors <- purrr::keep(lints, ~ .\\\$type == 'error');quit(save = 'no', status = if (length(errors) > 0) 1 else 0)\"") LINTER_COMMANDS_ARRAY_RAKU=(raku) LINTER_COMMANDS_ARRAY_RENOVATE=(renovate-config-validator --strict) LINTER_COMMANDS_ARRAY_RUBY=(rubocop -c "${RUBY_LINTER_RULES}" --force-exclusion --ignore-unrecognized-cops) -LINTER_COMMANDS_ARRAY_RUST_2015=(rustfmt --check --edition 2015) -LINTER_COMMANDS_ARRAY_RUST_2018=(rustfmt --check --edition 2018) -LINTER_COMMANDS_ARRAY_RUST_2021=(rustfmt --check --edition 2021) -# Consume the input as we do with ANSIBLE -LINTER_COMMANDS_ARRAY_RUST_CLIPPY=(cargo-clippy "&& echo \"Linted: {}\"") -LINTER_COMMANDS_ARRAY_SCALAFMT=(scalafmt --config "${SCALAFMT_LINTER_RULES}" --test) -LINTER_COMMANDS_ARRAY_SHELL_SHFMT=(shfmt -d) +LINTER_COMMANDS_ARRAY_RUST_2015=(rustfmt --edition 2015) +LINTER_COMMANDS_ARRAY_RUST_2018=(rustfmt --edition 2018) +LINTER_COMMANDS_ARRAY_RUST_2021=(rustfmt --edition 2021) +LINTER_COMMANDS_ARRAY_RUST_CLIPPY=(cargo-clippy) +LINTER_COMMANDS_ARRAY_SCALAFMT=(scalafmt --config "${SCALAFMT_LINTER_RULES}") +LINTER_COMMANDS_ARRAY_SHELL_SHFMT=(shfmt) LINTER_COMMANDS_ARRAY_SNAKEMAKE_LINT=(snakemake --lint -s) -LINTER_COMMANDS_ARRAY_SNAKEMAKE_SNAKEFMT=(snakefmt --config "${SNAKEMAKE_SNAKEFMT_LINTER_RULES}" --check --compact-diff) +LINTER_COMMANDS_ARRAY_SNAKEMAKE_SNAKEFMT=(snakefmt --config "${SNAKEMAKE_SNAKEFMT_LINTER_RULES}") LINTER_COMMANDS_ARRAY_STATES=(asl-validator --json-path) LINTER_COMMANDS_ARRAY_SQL=(sql-lint --config "${SQL_LINTER_RULES}") -LINTER_COMMANDS_ARRAY_SQLFLUFF=(sqlfluff lint --config "${SQLFLUFF_LINTER_RULES}") +LINTER_COMMANDS_ARRAY_SQLFLUFF=(sqlfluff) LINTER_COMMANDS_ARRAY_TEKTON=(tekton-lint) -LINTER_COMMANDS_ARRAY_TERRAFORM_FMT=(terraform fmt -check -diff) +LINTER_COMMANDS_ARRAY_TERRAFORM_FMT=(terraform fmt) LINTER_COMMANDS_ARRAY_TERRAFORM_TFLINT=("TF_DATA_DIR=\"/tmp/.terraform-TERRAFORM_TFLINT-{//}\"" tflint -c "${TERRAFORM_TFLINT_LINTER_RULES}" "--filter=\"{/}\"") LINTER_COMMANDS_ARRAY_TERRAFORM_TERRASCAN=(terrascan scan -i terraform -t all -c "${TERRAFORM_TERRASCAN_LINTER_RULES}" -f) LINTER_COMMANDS_ARRAY_TERRAGRUNT=(terragrunt hclfmt --terragrunt-check --terragrunt-log-level error --terragrunt-hclfmt-file) LINTER_COMMANDS_ARRAY_TSX=(eslint -c "${TSX_LINTER_RULES}") LINTER_COMMANDS_ARRAY_TYPESCRIPT_ES=(eslint -c "${TYPESCRIPT_ES_LINTER_RULES}") +LINTER_COMMANDS_ARRAY_TYPESCRIPT_PRETTIER=(prettier) LINTER_COMMANDS_ARRAY_TYPESCRIPT_STANDARD=(ts-standard --parser @typescript-eslint/parser --plugin @typescript-eslint/eslint-plugin --project "${TYPESCRIPT_STANDARD_TSCONFIG_FILE}") -LINTER_COMMANDS_ARRAY_TYPESCRIPT_PRETTIER=(prettier --check) LINTER_COMMANDS_ARRAY_XML=(xmllint) LINTER_COMMANDS_ARRAY_YAML=(yamllint -c "${YAML_LINTER_RULES}" -f parsable) if [ "${YAML_ERROR_ON_WARNING}" == 'true' ]; then LINTER_COMMANDS_ARRAY_YAML+=(--strict) fi + +function InitFixModeOptionsAndCommands() { + local LANGUAGE="${1}" + local FIX_MODE_VARIABLE_NAME="FIX_${LANGUAGE}" + debug "Check if ${LANGUAGE} command needs check only mode or fix mode options or commands by checking if ${FIX_MODE_VARIABLE_NAME} variable is defined." + + if [[ -v "${FIX_MODE_VARIABLE_NAME}" ]]; then + debug "${FIX_MODE_VARIABLE_NAME} is set. Check if we need to add check only mode options or fix mode options or commands." + + local FIX_MODE_OPTIONS_REF_VARIABLE_NAME="${LANGUAGE}_FIX_MODE_OPTIONS" + local -n FIX_MODE_OPTIONS_REF="${FIX_MODE_OPTIONS_REF_VARIABLE_NAME}" + + local OPTIONS_VARIABLE_NAME="${LANGUAGE}" + + local -n FIX_MODE_REF="${FIX_MODE_VARIABLE_NAME}" + if [[ "${FIX_MODE_REF}" == "true" ]]; then + debug "Fix mode for ${LANGUAGE} is enabled. Check if ${LANGUAGE} needs options to enable fix mode." + OPTIONS_VARIABLE_NAME="${OPTIONS_VARIABLE_NAME}_FIX_MODE_OPTIONS" + else + debug "Fix mode for ${LANGUAGE} is not enabled. Check if ${LANGUAGE} needs options to enable check mode." + OPTIONS_VARIABLE_NAME="${OPTIONS_VARIABLE_NAME}_CHECK_ONLY_MODE_OPTIONS" + fi + + local -a OPTIONS_TO_ADD_ARRAY + OPTIONS_TO_ADD_ARRAY=() + if [[ -v "${OPTIONS_VARIABLE_NAME}" ]]; then + local -n MODE_OPTIONS="${OPTIONS_VARIABLE_NAME}" + debug "${!MODE_OPTIONS} is defined. Contents: ${MODE_OPTIONS[*]}" + OPTIONS_TO_ADD_ARRAY=("${MODE_OPTIONS[@]}") + unset -n MODE_OPTIONS + else + debug "There are no options or commands for check only mode or fix mode to add at the end of the command for ${LANGUAGE}" + fi + + local -n LINTER_COMMAND_ARRAY="LINTER_COMMANDS_ARRAY_${LANGUAGE}" + debug "Load command for ${LANGUAGE}: ${!LINTER_COMMAND_ARRAY}. Contents: ${LINTER_COMMAND_ARRAY[*]}" + if [[ "${#OPTIONS_TO_ADD_ARRAY[@]}" -gt 0 ]]; then + debug "There are ${#OPTIONS_TO_ADD_ARRAY[@]} options to add at the end of the command for ${LANGUAGE}: ${OPTIONS_TO_ADD_ARRAY[*]}" + LINTER_COMMAND_ARRAY=("${LINTER_COMMAND_ARRAY[@]}" "${OPTIONS_TO_ADD_ARRAY[@]}") + debug "Add options at the end of the command for ${LANGUAGE}. Result: ${LINTER_COMMAND_ARRAY[*]}" + else + debug "${LANGUAGE} doesn't need any further options or commands for fix mode or check mode." + fi + + debug "Completed the initialization of linter command for ${LANGUAGE}. Result: ${LINTER_COMMAND_ARRAY[*]}" + + unset -n LINTER_COMMAND_ARRAY + unset -n FIX_MODE_REF + unset -n FIX_MODE_OPTIONS_REF + else + debug "${FIX_MODE_VARIABLE_NAME} is not set. Don't add check only mode options or fix mode options or commands for ${LANGUAGE}." + fi +} + +function InitInputConsumeCommands() { + LINTER_COMMANDS_ARRAY_ANSIBLE+=("${INPUT_CONSUME_COMMAND[@]}") + LINTER_COMMANDS_ARRAY_GO_MODULES+=("${INPUT_CONSUME_COMMAND[@]}") + LINTER_COMMANDS_ARRAY_RUST_CLIPPY+=("${INPUT_CONSUME_COMMAND[@]}") +} + +function InitPowerShellCommand() { + debug "PowerShell command before initialization: ${LINTER_COMMANDS_ARRAY_POWERSHELL[*]}" + LINTER_COMMANDS_ARRAY_POWERSHELL=(pwsh -NoProfile -NoLogo -Command "\"${LINTER_COMMANDS_ARRAY_POWERSHELL[*]}; if (\\\${Error}.Count) { exit 1 }\"") + debug "PowerShell command after initialization: ${LINTER_COMMANDS_ARRAY_POWERSHELL[*]}" +} diff --git a/lib/functions/validation.sh b/lib/functions/validation.sh index 1e23a25a..1b6932e2 100755 --- a/lib/functions/validation.sh +++ b/lib/functions/validation.sh @@ -140,6 +140,51 @@ function ValidateValidationVariables() { done } +function ValidateCheckModeAndFixModeVariables() { + for LANGUAGE in "${LANGUAGE_ARRAY[@]}"; do + local FIX_MODE_OPTIONS_VARIABLE_NAME="${LANGUAGE}_FIX_MODE_OPTIONS" + local CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME="${LANGUAGE}_CHECK_ONLY_MODE_OPTIONS" + local FIX_MODE_VARIABLE_NAME="FIX_${LANGUAGE}" + debug "Check if ${LANGUAGE} supports fix mode by checking if ${FIX_MODE_OPTIONS_VARIABLE_NAME}, ${CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME}, or both variables are set." + if [[ -v "${FIX_MODE_OPTIONS_VARIABLE_NAME}" ]] || + [[ -v "${CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME}" ]]; then + debug "Assuming that ${LANGUAGE} supports fix mode because ${FIX_MODE_OPTIONS_VARIABLE_NAME}, ${CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME}, or both variables are set." + + local -n FIX_MODE_REF="${FIX_MODE_VARIABLE_NAME}" + if [[ -n "${FIX_MODE_REF:-}" ]]; then + debug "The configuration provided a value for ${FIX_MODE_VARIABLE_NAME}: ${FIX_MODE_REF}" + else + FIX_MODE_REF="false" + debug "The configuration didn't provide a value for ${FIX_MODE_VARIABLE_NAME} for ${LANGUAGE}. Setting it to: ${FIX_MODE_REF}" + fi + + # TODO: After refactoring ValidateBooleanVariable to return an error instead + # of exiting the whole program, add a test case for when ValidateBooleanVariable fails + ValidateBooleanVariable "${!FIX_MODE_REF}" "${FIX_MODE_REF}" + + local -n VALIDATE_MODE_REF="VALIDATE_${LANGUAGE}" + + if [[ "${FIX_MODE_REF}" == "true" ]] && [[ "${VALIDATE_MODE_REF}" == "false" ]]; then + error "Cannot set ${!FIX_MODE_REF} to ${FIX_MODE_REF} when ${!VALIDATE_MODE_REF} is ${VALIDATE_MODE_REF}" + return 1 + fi + + export FIX_MODE_REF + else + debug "Assuming that ${LANGUAGE} doesn't support fix mode because it doesn't have ${FIX_MODE_OPTIONS_VARIABLE_NAME}, nor ${CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME} variables defined." + if [[ -v "${FIX_MODE_VARIABLE_NAME}" ]]; then + error "The configuration provided a value for ${FIX_MODE_VARIABLE_NAME} but it's not supported for ${LANGUAGE}" + return 1 + else + debug "The configuration didn't provide a value for ${FIX_MODE_VARIABLE_NAME} for ${LANGUAGE}" + fi + fi + + unset -n FIX_MODE_REF + unset -n VALIDATE_MODE_REF + done +} + function CheckIfGitBranchExists() { local BRANCH_NAME="${1}" debug "Check if the ${BRANCH_NAME} branch exists in ${GITHUB_WORKSPACE}" diff --git a/lib/functions/worker.sh b/lib/functions/worker.sh index 84184eb9..f43443d4 100755 --- a/lib/functions/worker.sh +++ b/lib/functions/worker.sh @@ -142,6 +142,16 @@ function LintCodebase() { # shellcheck source=/dev/null source /action/lib/functions/linterCommands.sh + # Dynamically add arguments and commands to each linter command as needed + if ! InitFixModeOptionsAndCommands "${FILE_TYPE}"; then + fatal "Error while inizializing fix mode and check only options and commands before running linter for ${FILE_TYPE}" + fi + InitInputConsumeCommands + + if [[ "${FILE_TYPE}" == "POWERSHELL" ]]; then + debug "Language: ${FILE_TYPE}. Initialize PowerShell command" + InitPowerShellCommand + fi local -n LINTER_COMMAND_ARRAY LINTER_COMMAND_ARRAY="LINTER_COMMANDS_ARRAY_${FILE_TYPE}" diff --git a/lib/globals/linterCommandsOptions.sh b/lib/globals/linterCommandsOptions.sh new file mode 100755 index 00000000..5572a5bc --- /dev/null +++ b/lib/globals/linterCommandsOptions.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2034 # Disable ununsed variables warning because we +# source this script and use these variables as globals + +# "check only" mode options for linters that that we reuse across several languages +PRETTIER_CHECK_ONLY_MODE_OPTIONS=(--check) +RUSTFMT_CHECK_ONLY_MODE_OPTIONS=(--check) + +# Define configuration options to enable "check only" mode. +# Some linters and formatters only support a "check only" mode so there's no +# need to define a "check only" mode option for those. +CLANG_FORMAT_CHECK_ONLY_MODE_OPTIONS=(--dry-run) +CSHARP_CHECK_ONLY_MODE_OPTIONS=(--verify-no-changes) +GOOGLE_JAVA_FORMAT_CHECK_ONLY_MODE_OPTIONS=(--dry-run --set-exit-if-changed) +JAVASCRIPT_PRETTIER_CHECK_ONLY_MODE_OPTIONS=("${PRETTIER_CHECK_ONLY_MODE_OPTIONS[@]}") +PYTHON_BLACK_CHECK_ONLY_MODE_OPTIONS=(--diff --check) +PYTHON_ISORT_CHECK_ONLY_MODE_OPTIONS=(--diff --check) +RUST_2015_CHECK_ONLY_MODE_OPTIONS=("${RUSTFMT_CHECK_ONLY_MODE_OPTIONS[@]}") +RUST_2018_CHECK_ONLY_MODE_OPTIONS=("${RUSTFMT_CHECK_ONLY_MODE_OPTIONS[@]}") +RUST_2021_CHECK_ONLY_MODE_OPTIONS=("${RUSTFMT_CHECK_ONLY_MODE_OPTIONS[@]}") +SCALAFMT_CHECK_ONLY_MODE_OPTIONS=(--test) +SHELL_SHFMT_CHECK_ONLY_MODE_OPTIONS=(--diff) +SNAKEMAKE_SNAKEFMT_CHECK_ONLY_MODE_OPTIONS=(--check --compact-diff) +SQLFLUFF_CHECK_ONLY_MODE_OPTIONS=(lint) +TERRAFORM_FMT_CHECK_ONLY_MODE_OPTIONS=(-check -diff) +TYPESCRIPT_PRETTIER_CHECK_ONLY_MODE_OPTIONS=("${PRETTIER_CHECK_ONLY_MODE_OPTIONS[@]}") + +# Fix mode options for linters that that we reuse across several languages +ESLINT_FIX_MODE_OPTIONS=(--fix) +GOLANGCI_LINT_FIX_MODE_OPTIONS=(--fix) +PRETTIER_FIX_MODE_OPTIONS=(--write) +STANDARD_FIX_MODE_OPTIONS=(--fix) + +# Define configuration options to enable "fix mode". +# Not all linters and formatters support this. +ANSIBLE_FIX_MODE_OPTIONS=(--fix) +CSS_FIX_MODE_OPTIONS=(--fix) +ENV_FIX_MODE_OPTIONS=(fix) +GO_FIX_MODE_OPTIONS=("${GOLANGCI_LINT_FIX_MODE_OPTIONS[@]}") +GO_MODULES_FIX_MODE_OPTIONS=("${GOLANGCI_LINT_FIX_MODE_OPTIONS[@]}") +GROOVY_FIX_MODE_OPTIONS=(--fix) +JAVASCRIPT_ES_FIX_MODE_OPTIONS=("${ESLINT_FIX_MODE_OPTIONS[@]}") +JAVASCRIPT_PRETTIER_FIX_MODE_OPTIONS=("${PRETTIER_FIX_MODE_OPTIONS[@]}") +JAVASCRIPT_STANDARD_FIX_MODE_OPTIONS=("${STANDARD_FIX_MODE_OPTIONS[@]}") +JSON_FIX_MODE_OPTIONS=("${ESLINT_FIX_MODE_OPTIONS[@]}") +JSONC_FIX_MODE_OPTIONS=("${ESLINT_FIX_MODE_OPTIONS[@]}") +JSX_FIX_MODE_OPTIONS=("${ESLINT_FIX_MODE_OPTIONS[@]}") +MARKDOWN_FIX_MODE_OPTIONS=(--fix) +POWERSHELL_FIX_MODE_OPTIONS=(-Fix) +PROTOBUF_FIX_MODE_OPTIONS=(-fix) +PYTHON_RUFF_FIX_MODE_OPTIONS=(--fix) +RUBY_FIX_MODE_OPTIONS=(--autocorrect) +RUST_CLIPPY_FIX_MODE_OPTIONS=(--fix) +SHELL_SHFMT_FIX_MODE_OPTIONS=(--write) +SQLFLUFF_FIX_MODE_OPTIONS=(fix) +TSX_FIX_MODE_OPTIONS=("${ESLINT_FIX_MODE_OPTIONS[@]}") +TYPESCRIPT_ES_FIX_MODE_OPTIONS=("${ESLINT_FIX_MODE_OPTIONS[@]}") +TYPESCRIPT_PRETTIER_FIX_MODE_OPTIONS=("${PRETTIER_FIX_MODE_OPTIONS[@]}") +TYPESCRIPT_STANDARD_FIX_MODE_OPTIONS=("${STANDARD_FIX_MODE_OPTIONS[@]}") + +# sqlfluff is a special case because it needs a different subcommand and +# subcommand options +SQLFLUFF_SHARED_SUBCOMMAND_OPTIONS=(--config "${SQLFLUFF_LINTER_RULES}") +SQLFLUFF_CHECK_ONLY_MODE_OPTIONS+=("${SQLFLUFF_SHARED_SUBCOMMAND_OPTIONS[@]}") +SQLFLUFF_FIX_MODE_OPTIONS+=("${SQLFLUFF_SHARED_SUBCOMMAND_OPTIONS[@]}") + +# If there's no input argument, GNU Parallel adds a default {} at the end of the +# command it runs. In a few cases, such as ANSIBLE, GO_MODULES, and RUST_CLIPPY, +# consume the {} element by artifically adding it to the command to run because +# we need the input to set the working directory, but we don't need it appended +# at the end of the command. +# Setting the -n 0 GNU Parallel would not help in this case, because the input +# will not be passed to the --workdir option as well. +INPUT_CONSUME_COMMAND=("&& echo \"Linted: {}\"") diff --git a/lib/linter.sh b/lib/linter.sh index 797bc0b2..899a63c3 100755 --- a/lib/linter.sh +++ b/lib/linter.sh @@ -788,6 +788,18 @@ done # Load rules for special cases GetStandardRules "javascript" +############################################################################# +# Validate the environment that depends on linter rules variables being set # +############################################################################# + +# We need the variables defined in linterCommandsOptions to initialize FIX_.... +# variables. +# shellcheck source=/dev/null +source /action/lib/globals/linterCommandsOptions.sh +if ! ValidateCheckModeAndFixModeVariables; then + fatal "Error while validating the configuration fix mode for linters that support that" +fi + ################################# # Check for SSL cert and update # ################################# diff --git a/test/inspec/super-linter/controls/super_linter.rb b/test/inspec/super-linter/controls/super_linter.rb index bd46c0b1..f0e7812e 100644 --- a/test/inspec/super-linter/controls/super_linter.rb +++ b/test/inspec/super-linter/controls/super_linter.rb @@ -475,6 +475,7 @@ control "super-linter-validate-files" do "/action/lib/functions/validation.sh", "/action/lib/functions/worker.sh", "/action/lib/globals/languages.sh", + "/action/lib/globals/linterCommandsOptions.sh", "/action/lib/globals/linterRules.sh", "/action/lib/.automation/actionlint.yml", "/action/lib/.automation/.ansible-lint.yml", diff --git a/test/lib/linterCommandsTest.sh b/test/lib/linterCommandsTest.sh index e9cdf802..24b450b0 100755 --- a/test/lib/linterCommandsTest.sh +++ b/test/lib/linterCommandsTest.sh @@ -4,6 +4,9 @@ set -o errexit set -o nounset set -o pipefail +# shellcheck source=/dev/null +source "test/testUtils.sh" + # Default log level # shellcheck disable=SC2034 LOG_LEVEL="DEBUG" @@ -39,6 +42,11 @@ for LANGUAGE in "${LANGUAGE_ARRAY_FOR_LINTER_RULES[@]}"; do done ValidateValidationVariables +# Now we can load linter command options because they have +# dependencies on linter rules +# shellcheck source=/dev/null +source /action/lib/globals/linterCommandsOptions.sh + # The slim image might not have this variable defined if [[ ! -v ARM_TTK_PSD1 ]]; then ARM_TTK_PSD1="/usr/lib/microsoft/arm-ttk/arm-ttk.psd1" @@ -51,6 +59,13 @@ fi # shellcheck source=/dev/null source "lib/functions/linterCommands.sh" +# Initialize the variables we're going to use to verify tests before running tests +# because some tests modify LINTER_COMMANDS_xxx variables +BASE_LINTER_COMMANDS_ARRAY_ANSIBLE=("${LINTER_COMMANDS_ARRAY_ANSIBLE[@]}") +BASE_LINTER_COMMANDS_ARRAY_GO_MODULES=("${LINTER_COMMANDS_ARRAY_GO_MODULES[@]}") +BASE_LINTER_COMMANDS_ARRAY_JSCPD=("${LINTER_COMMANDS_ARRAY_JSCPD[@]}") +BASE_LINTER_COMMANDS_ARRAY_RUST_CLIPPY=("${LINTER_COMMANDS_ARRAY_RUST_CLIPPY[@]}") + function LinterCommandPresenceTest() { local FUNCTION_NAME FUNCTION_NAME="${FUNCNAME[0]}" @@ -83,10 +98,10 @@ function IgnoreGitIgnoredFilesJscpdCommandTest() { # shellcheck source=/dev/null source "lib/functions/linterCommands.sh" - EXPECTED_COMMAND=("${LINTER_COMMANDS_ARRAY_JSCPD[@]}" "${JSCPD_GITIGNORE_OPTION}") + EXPECTED_COMMAND=("${BASE_LINTER_COMMANDS_ARRAY_JSCPD[@]}" "${JSCPD_GITIGNORE_OPTION}") - if [[ "${LINTER_COMMANDS_ARRAY_JSCPD[*]}" == "${EXPECTED_COMMAND[*]}" ]]; then - debug "Command (${LINTER_COMMANDS_ARRAY_JSCPD[*]}) matches with the expected one (${EXPECTED_COMMAND[*]})" + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY_JSCPD" "EXPECTED_COMMAND"; then + fatal "${FUNCTION_NAME} test failed" fi notice "${FUNCTION_NAME} PASS" @@ -97,19 +112,138 @@ function JscpdCommandTest() { FUNCTION_NAME="${FUNCNAME[0]}" info "${FUNCTION_NAME} start" + # shellcheck disable=SC2034 + IGNORE_GITIGNORED_FILES="false" + # Source the file again so it accounts for modifications # shellcheck source=/dev/null source "lib/functions/linterCommands.sh" - EXPECTED_COMMAND=(jscpd --config "${JSCPD_LINTER_RULES}") + # shellcheck disable=SC2034 + EXPECTED_COMMAND=("${BASE_LINTER_COMMANDS_ARRAY_JSCPD[@]}") - if [[ "${LINTER_COMMANDS_ARRAY_JSCPD[*]}" == "${EXPECTED_COMMAND[*]}" ]]; then - debug "Command (${LINTER_COMMANDS_ARRAY_JSCPD[*]}) matches with the expected one (${EXPECTED_COMMAND[*]})" + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY_JSCPD" "EXPECTED_COMMAND"; then + fatal "${FUNCTION_NAME} test failed" fi notice "${FUNCTION_NAME} PASS" } +function InitInputConsumeCommandsTest() { + local FUNCTION_NAME + FUNCTION_NAME="${FUNCNAME[0]}" + info "${FUNCTION_NAME} start" + + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_ANSIBLE=("${BASE_LINTER_COMMANDS_ARRAY_ANSIBLE[@]}" "${INPUT_CONSUME_COMMAND[@]}") + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_GO_MODULES=("${BASE_LINTER_COMMANDS_ARRAY_GO_MODULES[@]}" "${INPUT_CONSUME_COMMAND[@]}") + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_RUST_CLIPPY=("${BASE_LINTER_COMMANDS_ARRAY_RUST_CLIPPY[@]}" "${INPUT_CONSUME_COMMAND[@]}") + + if ! InitInputConsumeCommands; then + fatal "Error while initializing GNU parallel input consume commands" + fi + + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY_ANSIBLE" "EXPECTED_LINTER_COMMANDS_ARRAY_ANSIBLE"; then + fatal "${FUNCTION_NAME} test failed" + fi + + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY_GO_MODULES" "EXPECTED_LINTER_COMMANDS_ARRAY_GO_MODULES"; then + fatal "${FUNCTION_NAME} test failed" + fi + + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY_RUST_CLIPPY" "EXPECTED_LINTER_COMMANDS_ARRAY_RUST_CLIPPY"; then + fatal "${FUNCTION_NAME} test failed" + fi + + notice "${FUNCTION_NAME} PASS" +} + +function InitFixModeOptionsAndCommandsTest() { + local FUNCTION_NAME + FUNCTION_NAME="${FUNCNAME[0]}" + info "${FUNCTION_NAME} start" + + LANGUAGE_ARRAY=("A" "B" "C") + + # Test a command that has only fix mode options to add + # shellcheck disable=SC2034 + A_FIX_MODE_OPTIONS=(--fixA) + + # Test a command that has only check only mode options to add + # shellcheck disable=SC2034 + B_CHECK_ONLY_MODE_TEST=(--checkB) + + # Test a command that has both fix mode and check only mode options to add + # shellcheck disable=SC2034 + C_CHECK_ONLY_MODE_TEST=(--checkC) + # shellcheck disable=SC2034 + C_FIX_MODE_OPTIONS=(--fixC) + + for LANGUAGE in "${LANGUAGE_ARRAY[@]}"; do + local -n FIX_LANGUAGE_VARIABLE_NAME="FIX_${LANGUAGE}" + + # shellcheck disable=SC2034 + FIX_LANGUAGE_VARIABLE_NAME="true" + + local -n LINTER_COMMANDS_ARRAY="LINTER_COMMANDS_ARRAY_${LANGUAGE}" + # shellcheck disable=SC2034 + LINTER_COMMANDS_ARRAY=("LINTER_COMMANDS_ARRAY_FOR_LINTER_${LANGUAGE}_FIX_MODE_TEST") + EXPECTED_LINTER_COMMANDS_ARRAY_FIX_MODE=("${LINTER_COMMANDS_ARRAY[@]}") + local FIX_MODE_OPTIONS_VARIABLE_NAME="${LANGUAGE}_FIX_MODE_OPTIONS" + if [[ -v "${FIX_MODE_OPTIONS_VARIABLE_NAME}" ]]; then + local -n FIX_MODE_OPTIONS="${FIX_MODE_OPTIONS_VARIABLE_NAME}" + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_FIX_MODE+=("${FIX_MODE_OPTIONS[@]}") + unset -n FIX_MODE_OPTIONS + fi + if ! InitFixModeOptionsAndCommands "${LANGUAGE}"; then + fatal "InitFixModeOptionsAndCommands for ${LANGUAGE} should have passed validation" + fi + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY" "EXPECTED_LINTER_COMMANDS_ARRAY_FIX_MODE"; then + fatal "${FUNCTION_NAME} ${!FIX_LANGUAGE_VARIABLE_NAME}: ${FIX_LANGUAGE_VARIABLE_NAME} test failed" + fi + + # shellcheck disable=SC2034 + FIX_LANGUAGE_VARIABLE_NAME="false" + LINTER_COMMANDS_ARRAY=("LINTER_COMMANDS_ARRAY_FOR_LINTER_${LANGUAGE}_CHECK_ONLY_MODE_TEST") + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_CHECK_ONLY_MODE=("${LINTER_COMMANDS_ARRAY[@]}") + local CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME="${LANGUAGE}_CHECK_ONLY_MODE_OPTIONS" + if [[ -v "${CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME}" ]]; then + local -n CHECK_ONLY_MODE_OPTIONS="${CHECK_ONLY_MODE_OPTIONS_VARIABLE_NAME}" + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_CHECK_ONLY_MODE+=("${CHECK_ONLY_MODE_OPTIONS[@]}") + unset -n CHECK_ONLY_MODE_OPTIONS + fi + if ! InitFixModeOptionsAndCommands "${LANGUAGE}"; then + fatal "InitFixModeOptionsAndCommands for ${LANGUAGE} should have passed validation" + fi + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY" "EXPECTED_LINTER_COMMANDS_ARRAY_CHECK_ONLY_MODE"; then + fatal "${FUNCTION_NAME} ${!FIX_LANGUAGE_VARIABLE_NAME}: ${FIX_LANGUAGE_VARIABLE_NAME} test failed" + fi + + unset -n FIX_LANGUAGE_VARIABLE_NAME + unset -n LINTER_COMMANDS_ARRAY + done + + notice "${FUNCTION_NAME} PASS" +} + +function InitPowerShellCommandTest() { + # shellcheck disable=SC2034 + EXPECTED_LINTER_COMMANDS_ARRAY_POWERSHELL=(pwsh -NoProfile -NoLogo -Command "\"${LINTER_COMMANDS_ARRAY_POWERSHELL[*]}; if (\\\${Error}.Count) { exit 1 }\"") + InitPowerShellCommand + + if ! AssertArraysElementsContentMatch "LINTER_COMMANDS_ARRAY_POWERSHELL" "EXPECTED_LINTER_COMMANDS_ARRAY_POWERSHELL"; then + fatal "${FUNCTION_NAME} test failed" + fi +} + LinterCommandPresenceTest IgnoreGitIgnoredFilesJscpdCommandTest JscpdCommandTest +InitInputConsumeCommandsTest +InitFixModeOptionsAndCommandsTest +InitPowerShellCommandTest diff --git a/test/lib/validationTest.sh b/test/lib/validationTest.sh index f7295469..0c30e2bd 100755 --- a/test/lib/validationTest.sh +++ b/test/lib/validationTest.sh @@ -363,7 +363,7 @@ function ValidationVariablesExportTest() { for LANGUAGE in "${LANGUAGE_ARRAY[@]}"; do local -n VALIDATE_LANGUAGE VALIDATE_LANGUAGE="VALIDATE_${LANGUAGE}" - debug "VALIDATE_LANGUAGE (${LANGUAGE}) variable attributes: ${VALIDATE_LANGUAGE@a}" + debug "VALIDATE_LANGUAGE (Language: ${LANGUAGE}) variable attributes: ${VALIDATE_LANGUAGE@a}" if [[ "${VALIDATE_LANGUAGE@a}" == *x* ]]; then info "VALIDATE_LANGUAGE for ${LANGUAGE} is exported as expected" else @@ -375,6 +375,130 @@ function ValidationVariablesExportTest() { notice "${FUNCTION_NAME} PASS" } +function ValidateCheckModeAndFixModeVariablesTest() { + local FUNCTION_NAME + FUNCTION_NAME="${FUNCNAME[0]}" + info "${FUNCTION_NAME} start" + + # shellcheck disable=SC2034 + LANGUAGE_ARRAY=('A' 'B' 'C' 'D') + # shellcheck disable=SC2034 + A_FIX_MODE_OPTIONS=(--fixA) + # shellcheck disable=SC2034 + A_CHECK_ONLY_MODE_OPTIONS=(--checkA) + # shellcheck disable=SC2034 + B_FIX_MODE_OPTIONS=(--fixB) + # shellcheck disable=SC2034 + C_CHECK_ONLY_MODE_OPTIONS=(--checkC) + + # shellcheck disable=SC2034 + VALIDATE_A="true" + # shellcheck disable=SC2034 + VALIDATE_B="true" + # shellcheck disable=SC2034 + VALIDATE_C="true" + # shellcheck disable=SC2034 + FIX_B="true" + + if ! ValidateCheckModeAndFixModeVariables; then + fatal "Error while validating fix mode variables" + fi + + if [[ -v FIX_A ]]; then + debug "FIX_A variable is defined as expected" + else + fatal "FIX_A variable should have been defined" + fi + + EXPECTED_FIX_A="false" + if [[ "${FIX_A}" == "${EXPECTED_FIX_A}" ]]; then + debug "FIX_A variable has the expected value: ${FIX_A}" + else + fatal "FIX_A (${FIX_A}) doesn't match with the expected value: ${EXPECTED_FIX_A}" + fi + + if [[ -v FIX_C ]]; then + debug "FIX_C variable is defined as expected" + else + fatal "FIX_C variable should have been defined" + fi + + EXPECTED_FIX_C="false" + if [[ "${FIX_C}" == "${EXPECTED_FIX_C}" ]]; then + debug "FIX_C variable has the expected value: ${FIX_C}" + else + fatal "FIX_C (${FIX_C}) doesn't match with the expected value: ${EXPECTED_FIX_C}" + fi + + # No need to check if FIX_B is defined because we defined it earlier in this test function + + if [[ ! -v FIX_D ]]; then + debug "FIX_D is not defined as expected" + else + fatal "FIX_D variable should have not been defined" + fi + + debug "FIX_A variable attributes: ${FIX_A@a}" + if [[ "${FIX_A@a}" == *x* ]]; then + debug "FIX_A is exported as expected" + else + fatal "FIX_A should have been exported" + fi + + debug "FIX_B variable attributes: ${FIX_B@a}" + if [[ "${FIX_B@a}" == *x* ]]; then + debug "FIX_B is exported as expected" + else + fatal "FIX_B should have been exported" + fi + + debug "FIX_C variable attributes: ${FIX_C@a}" + if [[ "${FIX_C@a}" == *x* ]]; then + debug "FIX_C is exported as expected" + else + fatal "FIX_C should have been exported" + fi + + unset FIX_A + unset FIX_B + unset FIX_C + unset FIX_D + unset VALIDATE_A + unset VALIDATE_B + unset VALIDATE_C + + # shellcheck disable=SC2034 + LANGUAGE_ARRAY=('E') + # shellcheck disable=SC2034 + E_FIX_MODE_OPTIONS=(--fixA) + FIX_E="true" + VALIDATE_E="false" + + if ValidateCheckModeAndFixModeVariables; then + fatal "FIX_E (${FIX_E}) and VALIDATE_E (${VALIDATE_E}) should have failed validation" + else + debug "FIX_E (${FIX_E}) and VALIDATE_E (${VALIDATE_E}) failed validation as expected" + fi + + unset FIX_E + unset VALIDATE_E + + # shellcheck disable=SC2034 + LANGUAGE_ARRAY=('F') + FIX_F="true" + + if ValidateCheckModeAndFixModeVariables; then + fatal "FIX_F (${FIX_F}) should have failed validation when it doesn't support fix mode or check only mode" + else + debug "FIX_F (${FIX_F}) failed validation as expected when it doesn't support fix mode or check only mode" + fi + + unset FIX_F + unset VALIDATE_F + + notice "${FUNCTION_NAME} PASS" +} + IsUnsignedIntegerSuccessTest IsUnsignedIntegerFailureTest ValidateDeprecatedVariablesTest @@ -384,3 +508,4 @@ ValidateFindModeTest ValidateAnsibleDirectoryTest ValidateValidationVariablesTest ValidationVariablesExportTest +ValidateCheckModeAndFixModeVariablesTest diff --git a/test/linters/groovy/groovy_good_01.groovy b/test/linters/groovy/groovy_good_01.groovy index 8161e0d2..c59087dd 100644 --- a/test/linters/groovy/groovy_good_01.groovy +++ b/test/linters/groovy/groovy_good_01.groovy @@ -2,8 +2,10 @@ // file name for tests. // groovylint-disable-next-line ClassNameSameAsFilename class Example { + static void main(String[] args) { - File file = new File("E:/Example.txt") + File file = new File('E:/Example.txt') println "The file ${file.absolutePath} has ${file.length()} bytes" } + } diff --git a/test/run-super-linter-tests.sh b/test/run-super-linter-tests.sh index 9f268204..dc066061 100755 --- a/test/run-super-linter-tests.sh +++ b/test/run-super-linter-tests.sh @@ -4,6 +4,9 @@ set -o errexit set -o nounset set -o pipefail +# shellcheck source=/dev/null +source "test/testUtils.sh" + SUPER_LINTER_TEST_CONTAINER_URL="${1}" TEST_FUNCTION_NAME="${2}" SUPER_LINTER_CONTAINER_IMAGE_TYPE="${3}" @@ -151,6 +154,48 @@ run_test_case_custom_summary() { SUPER_LINTER_SUMMARY_FILE_NAME="custom-github-step-summary.md" } +run_test_case_fix_mode() { + run_test_cases_expect_success + CREATE_LOG_FILE="true" + SAVE_SUPER_LINTER_OUTPUT="true" + + COMMAND_TO_RUN+=(--env FIX_ANSIBLE="true") + COMMAND_TO_RUN+=(--env FIX_CLANG_FORMAT="true") + COMMAND_TO_RUN+=(--env FIX_CSHARP="true") + COMMAND_TO_RUN+=(--env FIX_CSS="true") + COMMAND_TO_RUN+=(--env FIX_ENV="true") + COMMAND_TO_RUN+=(--env FIX_GO_MODULES="true") + COMMAND_TO_RUN+=(--env FIX_GO="true") + COMMAND_TO_RUN+=(--env FIX_GOOGLE_JAVA_FORMAT="true") + COMMAND_TO_RUN+=(--env FIX_GROOVY="true") + COMMAND_TO_RUN+=(--env FIX_JAVASCRIPT_ES="true") + COMMAND_TO_RUN+=(--env FIX_JAVASCRIPT_PRETTIER="true") + COMMAND_TO_RUN+=(--env FIX_JAVASCRIPT_STANDARD="true") + COMMAND_TO_RUN+=(--env FIX_JSON="true") + COMMAND_TO_RUN+=(--env FIX_JSONC="true") + COMMAND_TO_RUN+=(--env FIX_JSX="true") + COMMAND_TO_RUN+=(--env FIX_MARKDOWN="true") + COMMAND_TO_RUN+=(--env FIX_POWERSHELL="true") + COMMAND_TO_RUN+=(--env FIX_PROTOBUF="true") + COMMAND_TO_RUN+=(--env FIX_PYTHON_BLACK="true") + COMMAND_TO_RUN+=(--env FIX_PYTHON_ISORT="true") + COMMAND_TO_RUN+=(--env FIX_PYTHON_RUFF="true") + COMMAND_TO_RUN+=(--env FIX_RUBY="true") + COMMAND_TO_RUN+=(--env FIX_RUST_2015="true") + COMMAND_TO_RUN+=(--env FIX_RUST_2018="true") + COMMAND_TO_RUN+=(--env FIX_RUST_2021="true") + COMMAND_TO_RUN+=(--env FIX_RUST_CLIPPY="true") + COMMAND_TO_RUN+=(--env FIX_SCALAFMT="true") + COMMAND_TO_RUN+=(--env FIX_SHELL_SHFMT="true") + COMMAND_TO_RUN+=(--env FIX_SNAKEMAKE_SNAKEFMT="true") + COMMAND_TO_RUN+=(--env FIX_SQLFLUFF="true") + COMMAND_TO_RUN+=(--env FIX_TERRAFORM_FMT="true") + COMMAND_TO_RUN+=(--env FIX_TSX="true") + COMMAND_TO_RUN+=(--env FIX_TYPESCRIPT_ES="true") + COMMAND_TO_RUN+=(--env FIX_TYPESCRIPT_PRETTIER="true") + COMMAND_TO_RUN+=(--env FIX_TYPESCRIPT_STANDARD="true") +} + # Run the test setup function ${TEST_FUNCTION_NAME} @@ -316,3 +361,8 @@ for item in "${TEMP_ITEMS_TO_CLEAN[@]}"; do echo "${item} does not exist as expected" fi done + +if ! CheckUnexpectedGitChanges "$(pwd)"; then + echo "There are unexpected modifications to the working directory after running tests." + exit 1 +fi diff --git a/test/testUtils.sh b/test/testUtils.sh new file mode 100755 index 00000000..0ccf5c64 --- /dev/null +++ b/test/testUtils.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# Default log level +# shellcheck disable=SC2034 +LOG_LEVEL="DEBUG" + +# shellcheck source=/dev/null +source "lib/functions/log.sh" + +function AssertArraysElementsContentMatch() { + local ARRAY_1_VARIABLE_NAME="${1}" + local ARRAY_2_VARIABLE_NAME="${2}" + local -n ARRAY_1="${ARRAY_1_VARIABLE_NAME}" + local -n ARRAY_2="${ARRAY_2_VARIABLE_NAME}" + if [[ "${ARRAY_1[*]}" == "${ARRAY_2[*]}" ]]; then + debug "${ARRAY_1_VARIABLE_NAME} (${ARRAY_1[*]}) matches the expected value: ${ARRAY_2[*]}" + RETURN_CODE=0 + else + error "${ARRAY_1_VARIABLE_NAME} (${ARRAY_1[*]}) doesn't match the expected value: ${ARRAY_2[*]}" + RETURN_CODE=1 + fi + unset -n ARRAY_1 + unset -n ARRAY_2 + return ${RETURN_CODE} +} + +function CheckUnexpectedGitChanges() { + local GIT_REPOSITORY_PATH="${1}" + # Check if there are unexpected changes in the working directory: + # - Unstaged changes + # - Changes that are staged but not committed + # - Untracked files and directories + if ! git -C "${GIT_REPOSITORY_PATH}" diff --exit-code --quiet || + ! git -C "${GIT_REPOSITORY_PATH}" diff --cached --exit-code --quiet || + ! git -C "${GIT_REPOSITORY_PATH}" ls-files --others --exclude-standard --directory; then + echo "There are unexpected changes in the working directory of the ${GIT_REPOSITORY_PATH} Git repository." + git -C "${GIT_REPOSITORY_PATH}" status + return 1 + fi +}