#!/bin/bash# run static code checks like eslint, mypy, ruff, vulture.set -eu# requires: pyproject.toml# requires: containers/flatpak/test/ruff.toml# requires: pkg/ruff.toml# requires: test/common/ruff.toml# requires: test/example/ruff.toml# requires: test/verify/ruff.toml# requires: tools/vulture_suppressions/ruff.toml# we consider any function named test_* to be a test case# each test is considered to succeed if it exits with no output# exit with status 77 is a skip, with the message in the output# otherwise, any output is a failure, even if exit status is 0# note: `set -e` is not active during the tests.find_scripts() {# Helper to find all scripts in the tree(# Any non-binary file which contains a given shebanggit grep --cached -lIz '^#!.*'"$1"shift# Any file matching the provided globsgit ls-files -z "$@") | sort -z | uniq -z}find_python_files() {find_scripts 'python3' '*.py'}test_ruff() {command -v ruff >/dev/null || skip 'no ruff'find_python_files | xargs -r -0 ruff check --quiet --no-cache}if [ "${WITH_PARTIAL_TREE:-0}" = 0 ]; thentest_mypy() {command -v mypy >/dev/null || skip 'no mypy'for file in $(cat test/mypy-deps 2>/dev/null || true); dotest -e "${file}" || skip "no ${file}"donefind_python_files | xargs -r -0 mypy --no-error-summary}test_vulture() {# vulture to find unused variables/functionscommand -v vulture >/dev/null || skip 'no vulture'find_python_files | xargs -r -0 vulture}fitest_js_translatable_strings() {# Translatable strings must be marked with _(""), not _('')! git grep -n -E "(gettext|_)\(['\`]" -- {src,pkg}/'*'.{js,ts,jsx,tsx}}if [ "${WITH_PARTIAL_TREE:-0}" = 0 ]; thentest_eslint() {test -x node_modules/.bin/eslint -a -x /usr/bin/node || skip 'no eslint'find_scripts 'node' '*.[jt]s' '*.[jt]sx' | xargs -0 node_modules/.bin/eslint}test_typescript() {test/common/typecheck}fitest_patternfly_barrel_file() {! git grep -n "@patternfly/react-core[^/]" -- {src,pkg}/'*'.{js,ts,jsx,tsx}}test_stylelint() {test -x node_modules/.bin/stylelint -a -x /usr/bin/node || skip 'no stylelint'git ls-files -z '*.css' '*.scss' | xargs -r -0 node_modules/.bin/stylelint}test_no_translatable_attr() {# Use of translatable attribute in HTML: should be 'translate' instead! git grep -n 'translatable=["'\'']yes' -- pkg doc}test_unsafe_security_policy() {# It's dangerous to have 'unsafe-inline' or 'unsafe-eval' in our# content-security-policy entries.git grep -lIz -E 'content-security-policy.*(\*|unsafe)' 'pkg/*/manifest.json' | while read -d '' filename; doif test ! -f "$(dirname ${filename})/content-security-policy.override"; thenecho "${filename} contains unsafe content security policy"fidone}test_json_verify() {# Check JSON files for validity: exclude top-level configs (which are extended json)git ls-files -z '*/*.json' | xargs -r -0 python3 -c "import argparseimport jsonparser = argparse.ArgumentParser()parser.add_argument('files', nargs='+', type=argparse.FileType(encoding='utf-8'))args = parser.parse_args()for file in args.files:try:json.load(file)except ValueError as exc: # both JSONDecodeError and UnicodeErrorprint(f'{file.name}: {exc}')"}test_html_verify() {# Check all HTML files for syntactic validitygit ls-files -z 'pkg/*.html' | while read -d '' filename; doif ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('${filename}')"; thenecho "${filename} contains invalid XML"fidone}test_include_config_h() {# Every C file should #include "config.h" at the topgit ls-files -cz '*.c' | while read -d '' filename; doif sed -n '/^#include "config.h"$/q1; /^\s*#/q;' "${filename}"; thenprintf '%s: #include "config.h" is not the first line\n' "${filename}"fidone}test_spdx_license_identifier() {# Every source file should have an SPDX-License-Identifier in the first 10 lines# Exclude file types that cannot have comments or don't need a licensegit ls-files pkg src -- \':!src/systemd/*' ':!src/cockpit/_vendor' \':!*.json' ':!*.png' ':!*.jpg' ':!*.gif' ':!*.svg' ':!*.ico' \':!*mock*' ':!*.pem' ':!*.key' ':!*.p12' \':!*.policy.in' ':!*.xml.in' ':!*.metainfo*' \':!*.md' ':!*.txt' ':!*.html' '!:*README' ':!*HACKING' \':!*.service' ':!*.desktop' | \while read -r filename; do# Check first 10 lines for SPDX-License-Identifierhead -n 10 "${filename}" | grep -q "SPDX-License-Identifier:" || \printf '%s: missing SPDX-License-Identifier in first 10 lines\n' "${filename}"done}### end of tests. start of machinery.skip() {printf "%s\n" "$*"exit 77}main() {if [ $# = 0 ]; thentap=''elif [ $# = 1 -a "$1" = "--tap" ]; thentap='1'elseprintf "usage: %s [--tap]\n" "$0" >&2exit 1ficd "${0%/*}/../.."if [ ! -e .git ]; thenecho '1..0 # SKIP not in a git checkout'exit 0fiexit_status=0counter=0tests=($(compgen -A function 'test_'))[ -n "${tap}" ] && printf "1..%d\n" "${#tests[@]}"for test_function in "${tests[@]}"; dopath="/static-code/$(echo ${test_function} | tr '_' '-')"counter=$((counter + 1))fail=''skip=''# run the test, capturing its output and exit statusoutput="$(${test_function} 2>&1)" && test_status=0 || test_status=$?if [ "${test_status}" = 77 ]; thenif [ -z "${tap}" ]; thenprintf >&2 "WARNING: skipping %s: %s\n" "${path}" "${output}"fiskip=" # SKIP ${output}"output=''elif [ "${test_status}" != 0 -o -n "${output}" ]; thenexit_status=1fail=1fi# Only print output on failures or --tap mode[ -n "${tap}" -o -n "${fail}" ] || continue# excluding the plan, this is the only output that we ever generateprintf "%s %d %s%s\n" "${fail:+not }ok" "${counter}" "${path}" "${skip}"if [ -n "${output}" ]; thenprintf "%s\n" "${output}" | sed -e 's/^/# /'fidoneexit "${exit_status}"}main "$@"