Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bin/run-linters.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ for linter in "${REQUIRED_LINTERS[@]}"; do
run_args=("${run_args_common[@]}")
"${LIB_ROOT}/checks/linters/markdownlint/run.sh" "${run_args[@]}"
;;
python)
run_args=("${run_args_common[@]}")
"${LIB_ROOT}/checks/linters/python/run.sh" "${run_args[@]}"
;;
codespell)
run_args=("${run_args_common[@]}")
"${LIB_ROOT}/checks/linters/codespell/run.sh" "${run_args[@]}"
Expand Down
8 changes: 8 additions & 0 deletions checks/detect-linters.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ linter_require_common_args
shellcheck_needed=0
groovylint_needed=0
markdownlint_needed=0
python_needed=0
codespell_needed=0
text_hygiene_needed=0
filename_portability_needed=0
Expand All @@ -34,6 +35,10 @@ while IFS= read -r file_path; do
if linter_is_markdown_candidate "${file_path}"; then
markdownlint_needed=1
fi

if linter_is_python_candidate "${file_path}"; then
python_needed=1
fi
done < <(linter_get_candidate_files_acmr)

if [[ ${shellcheck_needed} -eq 1 ]]; then
Expand All @@ -45,6 +50,9 @@ fi
if [[ ${markdownlint_needed} -eq 1 ]]; then
echo 'markdownlint'
fi
if [[ ${python_needed} -eq 1 ]]; then
echo 'python'
fi
if [[ ${codespell_needed} -eq 1 ]]; then
echo 'codespell'
fi
Expand Down
30 changes: 24 additions & 6 deletions checks/install-linter-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ for linter in "${REQUIRED_LINTERS[@]}"; do
shellcheck) PACKAGES+=("shellcheck") ;;
groovylint) PACKAGES+=("npm") ;;
markdownlint) PACKAGES+=("npm") ;;
python) PACKAGES+=("flake8" "pylint") ;;
codespell) PACKAGES+=("codespell") ;;
*) continue ;;
esac
Expand Down Expand Up @@ -321,12 +322,29 @@ else

LINUX_PACKAGES=()
for package in "${MISSING_PACKAGES[@]}"; do
if [[ "${package}" == "npm" ]]; then
# Ubuntu/WSL reliability: install both nodejs and npm together.
LINUX_PACKAGES+=("nodejs" "npm")
else
LINUX_PACKAGES+=("${package}")
fi
case "${package}" in
npm)
# Ubuntu/WSL reliability: install both nodejs and npm together.
LINUX_PACKAGES+=("nodejs" "npm")
;;
flake8)
if command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
LINUX_PACKAGES+=("python3-flake8")
else
LINUX_PACKAGES+=("flake8")
fi
;;
pylint)
if command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
LINUX_PACKAGES+=("python3-pylint")
else
LINUX_PACKAGES+=("pylint")
fi
;;
*)
LINUX_PACKAGES+=("${package}")
;;
esac
done

if command -v apt-get >/dev/null 2>&1; then
Expand Down
33 changes: 31 additions & 2 deletions checks/linter-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ linter_is_shell_script_candidate() {
return 0
fi

# Files without an extension may still be shell scripts when they
# declare a shell interpreter in a shebang line.
# Files with any extension are linted by their extension only.
if [[ "${base_name}" == *.* ]]; then
return 1
fi

# Files without an extension may declare a shell interpreter via a shebang.
Comment thread
janekmi marked this conversation as resolved.
IFS= read -r first_line < "${absolute_path}" || true
if printf '%s\n' "${first_line}" | LC_ALL=C grep -Eq \
'^#![[:space:]]*([^[:space:]]+/)?(env([[:space:]]+-S)?[[:space:]]+)?(bash|sh|dash|ksh|zsh)([[:space:]]|$)'; then
Expand Down Expand Up @@ -187,3 +187,32 @@ linter_is_markdown_candidate() {

return 1
}

linter_is_python_candidate() {
local file_path="$1"
local absolute_path="${TARGET_ROOT}/${file_path}"
local first_line=""

[[ -f "${absolute_path}" ]] || return 1

case "${file_path}" in
*.py)
return 0
;;
esac

# Files with any extension are linted by their extension only.
local base_name="${file_path##*/}"
if [[ "${base_name}" == *.* ]]; then
return 1
fi

# Files without an extension may declare a Python interpreter via a shebang.
IFS= read -r first_line < "${absolute_path}" || true
if printf '%s\n' "${first_line}" | LC_ALL=C grep -Eq \
'^#![[:space:]]*([^[:space:]]+/)?(env([[:space:]]+-S)?[[:space:]]+)?python([[:space:]]|$)'; then
return 0
fi
Comment thread
janekmi marked this conversation as resolved.

return 1
}
9 changes: 9 additions & 0 deletions checks/linters/python/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright 2026 Hewlett Packard Enterprise Development LP
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$scriptDir = Split-Path -Parent $PSCommandPath
$checksRoot = Split-Path -Parent (Split-Path -Parent $scriptDir)

. (Join-Path $checksRoot 'invoke-bash.ps1')
Invoke-BashScript -ScriptPath (Join-Path $scriptDir 'run.sh') -ScriptArgs $args
38 changes: 38 additions & 0 deletions checks/linters/python/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Copyright 2026 Hewlett Packard Enterprise Development LP
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/../../linter-common.sh"

linter_parse_common_args "$@"
linter_fail_on_unknown_args
linter_require_common_args

if ! command -v flake8 >/dev/null 2>&1; then
echo "flake8 is required but was not found in PATH" >&2
exit 127
fi
if ! command -v pylint >/dev/null 2>&1; then
echo "pylint is required but was not found in PATH" >&2
exit 127
fi

files_to_check=()
while IFS= read -r file_path; do
linter_should_skip_candidate_path "${file_path}" && continue

if linter_is_python_candidate "${file_path}"; then
files_to_check+=("${TARGET_ROOT}/${file_path}")
fi
done < <(linter_get_candidate_files_acmr)

if [[ ${#files_to_check[@]} -eq 0 ]]; then
echo "[python] no Python files to lint"
exit 0
fi

echo "[python] linting ${#files_to_check[@]} file(s)"
flake8 "${files_to_check[@]}"
pylint "${files_to_check[@]}"
Comment thread
janekmi marked this conversation as resolved.
3 changes: 3 additions & 0 deletions docs/linters.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The changed-file linter set currently includes:
- `groovylint` (Groovy and Jenkins DSL linting for `*.groovy`, `*.gradle`,
and `Jenkinsfile*`)
- `markdownlint` (Markdown linting for `*.md` and `*.markdown`)
- `python` (shared Python linting for `*.py` using `flake8` and `pylint`)

Groovylint execution also includes a post-lint guard that rejects implicit
script-binding assignments (bare `name = value` at statement start) to prevent
Expand Down Expand Up @@ -122,6 +123,8 @@ Current tool preflight mapping:
- `markdownlint` linter requires the `markdownlint` CLI on PATH;
`markdownlint-cli` is installed via npm (see the markdownlint version note in
the Current Linters section above)
- `python` linter requires both `flake8` and `pylint` on PATH and runs both
tools against the same changed Python file set

On Linux/macOS targets, preflight failures include install hints for common
package managers.
Expand Down
1 change: 1 addition & 0 deletions vscode-project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ nicolasvuillamy
nonblank
Pipenv
pyenv
pylint
pylintrc
pyproject
pyyaml
Expand Down