diff --git a/.github/workflows/cgl.yml b/.github/workflows/cgl.yml index 41a202c..9335f6c 100644 --- a/.github/workflows/cgl.yml +++ b/.github/workflows/cgl.yml @@ -42,6 +42,10 @@ jobs: core: '13.0' - php: '8.3' core: '13.0' + - php: '8.2' + core: '13.1' + - php: '8.3' + core: '13.1' - php: '8.2' core: 'main' - php: '8.3' diff --git a/.github/workflows/tests-13.1.yml b/.github/workflows/tests-13.1.yml new file mode 100644 index 0000000..ff1fdbe --- /dev/null +++ b/.github/workflows/tests-13.1.yml @@ -0,0 +1,26 @@ +name: tests 13.1 + +on: + push: + pull_request: + schedule: + - cron: '56 4 * * *' + +jobs: + unit: + name: Unit Tests + runs-on: ubuntu-latest + strategy: + matrix: + php: + - '8.2' + - '8.3' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install testing system + run: Build/Scripts/runTests.sh -t 13.1 -p ${{ matrix.php }} -s composerUpdate + + - name: Unit Tests + run: Build/Scripts/runTests.sh -t 13.1 -p ${{ matrix.php }} -s unit diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 85914b6..0718a10 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -1,101 +1,86 @@ #!/usr/bin/env bash # -# TYPO3 core test runner based on docker and docker-compose. +# EXT:examples test runner based on docker/podman. # -# Function to write a .env file in Build/testing-docker -# This is read by docker-compose and vars defined here are -# used in Build/testing-docker/docker-compose.yml -setUpDockerComposeDotEnv() { - # Delete possibly existing local .env file if exists - [ -e .env ] && rm .env - # Set up a new .env file for docker-compose - { - echo "COMPOSE_PROJECT_NAME=local" - # To prevent access rights of files created by the testing, the docker image later - # runs with the same user that is currently executing the script. docker-compose can't - # use $UID directly itself since it is a shell variable and not an env variable, so - # we have to set it explicitly here. - echo "HOST_UID=`id -u`" - # Your local user - echo "ROOT_DIR=${ROOT_DIR}" - echo "HOST_USER=${USER}" - echo "TEST_FILE=${TEST_FILE}" - echo "TYPO3_VERSION=${TYPO3_VERSION}" - echo "PHP_XDEBUG_ON=${PHP_XDEBUG_ON}" - echo "PHP_XDEBUG_PORT=${PHP_XDEBUG_PORT}" - echo "DOCKER_PHP_IMAGE=${DOCKER_PHP_IMAGE}" - echo "EXTRA_TEST_OPTIONS=${EXTRA_TEST_OPTIONS}" - echo "SCRIPT_VERBOSE=${SCRIPT_VERBOSE}" - echo "CGLCHECK_DRY_RUN=${CGLCHECK_DRY_RUN}" - echo "COMPOSER_NORMALIZE_DRY_RUN=${COMPOSER_NORMALIZE_DRY_RUN}" - } > .env +cleanUp() { + ATTACHED_CONTAINERS=$(${CONTAINER_BIN} ps --filter network=${NETWORK} --format='{{.Names}}') + for ATTACHED_CONTAINER in ${ATTACHED_CONTAINERS}; do + ${CONTAINER_BIN} rm -f ${ATTACHED_CONTAINER} >/dev/null + done + ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null } -# Load help text into $HELP -read -r -d '' HELP <=20.10 for xdebug break pointing to work reliably, and -a recent docker-compose (tested >=1.21.2) is needed. +cleanRenderedDocumentationFiles() { + echo -n "Clean rendered documentation files ... " + rm -rf \ + Documentation-GENERATED-temp + echo "done" +} -Usage: $0 [options] [file] +loadHelp() { + # Load help text into $HELP + read -r -d '' HELP < Specifies which test suite to run - cgl: cgl test and fix all php files - - clean: Cleanup non-repo files from testing + - clean: Clean temporary files + - cleanCache: Clean cache folds for files. + - cleanRenderedDocumentation: Clean existing rendered documentation output. + - composer: "composer" with all remaining arguments dispatched. - composerNormalize: "composer normalize" - composerUpdate: "composer update", handy if host has no PHP - composerValidate: "composer validate" - lint: PHP linting - - unit (default): PHP unit tests + - phpstan: PHPStan static analysis + - phpstanBaseline: Generate PHPStan baseline + - rector: Apply Rector rules + - renderDocumentation + - testRenderDocumentation + - unit + + -b + Container environment: + - docker + - podman + + If not specified, podman will be used if available. Otherwise, docker is used. - -p <8.1|8.2> + -p <8.1|8.2|8.3> Specifies the PHP minor version to be used - - 8.1 (default): use PHP 8.1 + - 8.1: (default) use PHP 8.1 - 8.2: use PHP 8.2 + - 8.3: use PHP 8.3 - -e "" - Only with -s unit - Additional options to send to phpunit (unit tests). - Options starting with "--" must be added after options starting with "-". - Example -e "-v --filter canRetrieveValueWithGP" to enable verbose output AND filter tests - named "canRetrieveValueWithGP" - - -t <12.4|13.0|main> + -t <12.4|13.0|13.1|main> Only with -s composerUpdate Specifies the TYPO3 core major version to be used - 12.4 (default): use TYPO3 core v12 - 13.0: use TYPO3 core v13.0 + - 13.1: use TYPO3 core v13.1 - main: use TYPO3 core main - - -x - Only with -s unit|acceptance - Send information to host instance for test or system under test break points. This is especially - useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port - can be selected with -y - - -y - Send xdebug information to a different port than default 9003 if an IDE like PhpStorm - is not listening on default port. - -n - Only with -s cgl, composerNormalize + Only with -s cgl, composerNormalize, rector Activate dry-run in CGL check and composer normalize that does not actively change files and only prints broken ones. -u - Update existing typo3/core-testing-*:latest docker images. Maintenance call to docker pull latest - versions of the main php images. The images are updated once in a while and only the youngest - ones are supported by core testing. Use this if weird test errors occur. Also removes obsolete - image versions of typo3/core-testing-*. - - -v - Enable verbose script output. Shows variables and docker commands. + Update existing typo3/core-testing-*:latest container images and remove dangling local volumes. + New images are published once in a while and only the latest ones are supported by core testing. + Use this if weird test errors occur. Also removes obsolete image versions of typo3/core-testing-*. -h Show this help. @@ -104,65 +89,42 @@ Examples: # Run unit tests using PHP 8.1 ./Build/Scripts/runTests.sh EOF +} -# Test if docker-compose exists, else exit out with error -if ! type "docker-compose" > /dev/null; then - echo "This script relies on docker and docker-compose. Please install" >&2 - exit 1 +# Test if docker exists, else exit out with error +if ! type "docker" >/dev/null 2>&1 && ! type "podman" >/dev/null 2>&1; then + echo "This script relies on docker or podman. Please install" >&2 + exit 1 fi -# Go to the directory this script is located, so everything else is relative -# to this dir, no matter from where this script is called. -THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd "$THIS_SCRIPT_DIR" || exit 1 - -# Go to directory that contains the local docker-compose.yml file -cd ../testing-docker || exit 1 - # Option defaults -if ! command -v realpath &> /dev/null; then - echo "This script works best with realpath installed" >&2 - ROOT_DIR="${PWD}/../../" -else - ROOT_DIR=`realpath ${PWD}/../../` -fi TEST_SUITE="cgl" PHP_VERSION="8.1" -TYPO3_VERSION="11.5" PHP_XDEBUG_ON=0 PHP_XDEBUG_PORT=9003 -SCRIPT_VERBOSE=0 -CGLCHECK_DRY_RUN="" -COMPOSER_NORMALIZE_DRY_RUN="" +CGLCHECK_DRY_RUN=0 +CI_PARAMS="${CI_PARAMS:-}" +DOCS_PARAMS="${DOCS_PARAMS:=--pull always}" +CONTAINER_BIN="" +CONTAINER_HOST="host.docker.internal" +TYPO3_VERSION="12.4" -# Option parsing +# Option parsing updates above default vars # Reset in case getopts has been used previously in the shell OPTIND=1 # Array for invalid options -INVALID_OPTIONS=(); -# Simple option parsing based on getopts (! not getopt). GNU getopts is available on linux and mac systems, -# where GNU getop may be not installed on mac systems. -while getopts ":s:p:t:e:xynhuv" OPT; do +INVALID_OPTIONS=() +# Simple option parsing based on getopts (! not getopt) +while getopts "b:s:p:t:xy:nhu" OPT; do case ${OPT} in - e) - EXTRA_TEST_OPTIONS=${OPTARG} - ;; - h) - echo "${HELP}" - exit 0 - ;; s) TEST_SUITE=${OPTARG} ;; - t) - TYPO3_VERSION=${OPTARG} - if ! [[ ${TYPO3_VERSION} =~ ^(12.4|13.0|main)$ ]]; then - INVALID_OPTIONS+=("t ${OPTARG}") + b) + if ! [[ ${OPTARG} =~ ^(docker|podman)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") fi - ;; - n) - CGLCHECK_DRY_RUN="-n" - COMPOSER_NORMALIZE_DRY_RUN="--dry-run" + CONTAINER_BIN=${OPTARG} ;; p) PHP_VERSION=${OPTARG} @@ -170,11 +132,11 @@ while getopts ":s:p:t:e:xynhuv" OPT; do INVALID_OPTIONS+=("p ${OPTARG}") fi ;; - u) - TEST_SUITE=update - ;; - v) - SCRIPT_VERBOSE=1 + t) + TYPO3_VERSION=${OPTARG} + if ! [[ ${TYPO3_VERSION} =~ ^(12.4|13.0|13.1|main)$ ]]; then + INVALID_OPTIONS+=("t ${OPTARG}") + fi ;; x) PHP_XDEBUG_ON=1 @@ -182,11 +144,22 @@ while getopts ":s:p:t:e:xynhuv" OPT; do y) PHP_XDEBUG_PORT=${OPTARG} ;; + n) + CGLCHECK_DRY_RUN=1 + ;; + h) + loadHelp + echo "${HELP}" + exit 0 + ;; + u) + TEST_SUITE=update + ;; \?) - INVALID_OPTIONS+=(${OPTARG}) + INVALID_OPTIONS+=("${OPTARG}") ;; :) - INVALID_OPTIONS+=(${OPTARG}) + INVALID_OPTIONS+=("${OPTARG}") ;; esac done @@ -198,91 +171,235 @@ if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then echo "-"${I} >&2 done echo >&2 - echo "${HELP}" >&2 + echo "call \".Build/Scripts/runTests.sh -h\" to display help and valid options" exit 1 fi -# Move "8.1" to "php81", the latter is the docker container name -DOCKER_PHP_IMAGE=`echo "php${PHP_VERSION}" | sed -e 's/\.//'` +COMPOSER_ROOT_VERSION="13.0.x-dev" +HOST_UID=$(id -u) +USERSET="" +if [ $(uname) != "Darwin" ]; then + USERSET="--user $HOST_UID" +fi + +# Go to the directory this script is located, so everything else is relative +# to this dir, no matter from where this script is called, then go up two dirs. +THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$THIS_SCRIPT_DIR" || exit 1 +cd ../../ || exit 1 +ROOT_DIR="${PWD}" + +# Create .cache dir: composer need this. +mkdir -p .Build/.cache +mkdir -p .Build/Web/typo3temp/var/tests + +IMAGE_PREFIX="docker.io/" +# Non-CI fetches TYPO3 images (php and nodejs) from ghcr.io +TYPO3_IMAGE_PREFIX="ghcr.io/typo3/" +CONTAINER_INTERACTIVE="-it --init" + +IS_CORE_CI=0 +# ENV var "CI" is set by gitlab-ci. We use it here to distinct 'local' and 'CI' environment. +if [ "${CI}" == "true" ]; then + IS_CORE_CI=1 + IMAGE_PREFIX="" + CONTAINER_INTERACTIVE="" +fi + +# determine default container binary to use: 1. podman 2. docker +if [[ -z "${CONTAINER_BIN}" ]]; then + if type "podman" >/dev/null 2>&1; then + CONTAINER_BIN="podman" + elif type "docker" >/dev/null 2>&1; then + CONTAINER_BIN="docker" + fi +fi + +IMAGE_PHP="${TYPO3_IMAGE_PREFIX}core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest" +IMAGE_ALPINE="${IMAGE_PREFIX}alpine:3.8" +IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest" # Set $1 to first mass argument, this is the optional test file or test directory to execute shift $((OPTIND - 1)) -TEST_FILE=${1} -if [ -n "${1}" ]; then - TEST_FILE="Web/typo3conf/ext/codesnippet/${1}" + +SUFFIX=$(echo $RANDOM) +NETWORK="t3docsexamples-${SUFFIX}" +${CONTAINER_BIN} network create ${NETWORK} >/dev/null + +if [ ${CONTAINER_BIN} = "docker" ]; then + # docker needs the add-host for xdebug remote debugging. podman has host.container.internal built in + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" + CONTAINER_DOCS_PARAMS="${CONTAINER_INTERACTIVE} ${DOCS_PARAMS} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${ROOT_DIR}:/project" +else + # podman + CONTAINER_HOST="host.containers.internal" + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" + CONTAINER_DOCS_PARAMS="${CONTAINER_INTERACTIVE} ${DOCS_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:/project" fi -if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x +if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE="-e XDEBUG_MODE=off" + XDEBUG_CONFIG=" " +else + XDEBUG_MODE="-e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=foo" + XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal" fi # Suite execution case ${TEST_SUITE} in cgl) - # Active dry-run for cgl needs not "-n" but specific options - if [[ ! -z ${CGLCHECK_DRY_RUN} ]]; then - CGLCHECK_DRY_RUN="--dry-run --diff" + if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then + COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v --dry-run --diff --config=Build/php-cs-fixer/.php-cs-fixer.dist.php --using-cache=no ." + else + COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v --config=Build/php-cs-fixer/.php-cs-fixer.dist.php --using-cache=no ." fi - setUpDockerComposeDotEnv - docker-compose run cgl + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" SUITE_EXIT_CODE=$? - docker-compose down ;; clean) - rm -rf \ - ../../composer.lock \ - ../../.Build/ \ - ../../.cache/ \ - ../../composer.json.testing \ - ../../var/ + cleanCacheFiles + cleanRenderedDocumentationFiles + ;; + cleanCache) + cleanCacheFiles + ;; + cleanRenderedDocumentation) + cleanRenderedDocumentationFiles + ;; + composer) + COMMAND=(composer "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? ;; composerNormalize) - setUpDockerComposeDotEnv - docker-compose run composer_normalize + if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then + COMMAND=(composer normalize --no-check-lock --no-update-lock -n) + else + COMMAND=(composer normalize --no-check-lock --no-update-lock) + fi + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? - docker-compose down ;; composerUpdate) - setUpDockerComposeDotEnv - cp ../../composer.json ../../composer.json.orig - if [ -f "../../composer.json.testing" ]; then - cp ../../composer.json ../../composer.json.orig + rm -rf .Build/bin/ .Build/typo3 .Build/vendor .Build/Web ./composer.lock + cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.orig + if [ -f "${ROOT_DIR}/composer.json.testing" ]; then + cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.orig + fi + if [ "${TYPO3_VERSION}" == "12.4" ]; then + COMMAND=(composer req typo3/cms-core:~12.4@dev -W --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" fi - docker-compose run composer_update - cp ../../composer.json ../../composer.json.testing - mv ../../composer.json.orig ../../composer.json + if [ "${TYPO3_VERSION}" == "13.0" ]; then + COMMAND=(composer req --dev --no-update typo3/cms-backend:~13.0.1@dev typo3/cms-recordlist:~13.0.1@dev typo3/cms-frontend:~13.0.1@dev typo3/cms-extbase:~13.0.1@dev typo3/cms-fluid:~13.0.1@dev typo3/cms-install:~13.0.1@dev --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-dev-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + COMMAND=(composer req typo3/cms-core:~13.0.1@dev -W --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + fi + if [ "${TYPO3_VERSION}" == "13.1" ]; then + COMMAND=(composer req --dev --no-update typo3/cms-backend:~13.1@dev typo3/cms-recordlist:~13.1@dev typo3/cms-frontend:~13.1@dev typo3/cms-extbase:~13.1@dev typo3/cms-fluid:~13.1@dev typo3/cms-install:~13.1@dev --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-dev-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + COMMAND=(composer req typo3/cms-core:~13.1@dev -W --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + fi + if [ "${TYPO3_VERSION}" == "main" ]; then + COMMAND=(composer req --dev --no-update typo3/cms-backend:dev-main typo3/cms-recordlist:dev-main typo3/cms-frontend:dev-main typo3/cms-extbase:dev-main typo3/cms-fluid:dev-main typo3/cms-install:dev-main --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-dev-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + COMMAND=(composer req typo3/cms-core:dev-main -W --no-update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-prepare-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + fi + COMMAND=(composer update --no-ansi --no-interaction --no-progress) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? - docker-compose down + cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.testing + mv ${ROOT_DIR}/composer.json.orig ${ROOT_DIR}/composer.json ;; composerValidate) - setUpDockerComposeDotEnv - docker-compose run composer_validate + COMMAND=(composer validate --no-check-lock "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? - docker-compose down ;; lint) - setUpDockerComposeDotEnv - docker-compose run lint + COMMAND="find . -name \\*.php ! -path "./.Build/\\*" -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" SUITE_EXIT_CODE=$? - docker-compose down ;; - unit) - setUpDockerComposeDotEnv - docker-compose run unit + phpstan) + COMMAND="php -dxdebug.mode=off .Build/bin/phpstan --configuration=Build/phpstan/phpstan.neon" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + phpstanBaseline) + COMMAND="php -dxdebug.mode=off .Build/bin/phpstan --configuration=Build/phpstan/phpstan.neon --generate-baseline=Build/phpstan/phpstan-baseline.neon -v" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name phpstan-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + rector) + if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then + COMMAND=(php -dxdebug.mode=off .Build/bin/rector -n --config=Build/rector/rector.php --clear-cache "$@") + else + COMMAND=(php -dxdebug.mode=off .Build/bin/rector --config=Build/rector/rector.php --clear-cache "$@") + fi + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name rector-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + renderDocumentation) + COMMAND=(--config=Documentation "$@") + mkdir -p Documentation-GENERATED-temp + ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} ${CONTAINER_DOCS_PARAMS} --name render-documentation-${SUFFIX} ${IMAGE_DOCS} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + testRenderDocumentation) + COMMAND=(--config=Documentation --no-progress --fail-on-log "$@") + mkdir -p Documentation-GENERATED-temp + ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} ${CONTAINER_DOCS_PARAMS} --name render-documentation-test-${SUFFIX} ${IMAGE_DOCS} "${COMMAND[@]}" SUITE_EXIT_CODE=$? - docker-compose down ;; update) - # pull typo3/core-testing-*:latest versions of those ones that exist locally - docker images typo3/core-testing-*:latest --format "{{.Repository}}:latest" | xargs -I {} docker pull {} + # pull typo3/core-testing-* versions of those ones that exist locally + echo "> pull ${TYPO3_IMAGE_PREFIX}core-testing-* versions of those ones that exist locally" + ${CONTAINER_BIN} images "${TYPO3_IMAGE_PREFIX}core-testing-*" --format "{{.Repository}}:{{.Tag}}" | xargs -I {} ${CONTAINER_BIN} pull {} + echo "" # remove "dangling" typo3/core-testing-* images (those tagged as ) - docker images typo3/core-testing-* --filter "dangling=true" --format "{{.ID}}" | xargs -I {} docker rmi {} + echo "> remove \"dangling\" ${TYPO3_IMAGE_PREFIX}/core-testing-* images (those tagged as )" + ${CONTAINER_BIN} images --filter "reference=${TYPO3_IMAGE_PREFIX}/core-testing-*" --filter "dangling=true" --format "{{.ID}}" | xargs -I {} ${CONTAINER_BIN} rmi -f {} + echo "" + ;; + unit) + COMMAND="php -dxdebug.mode=off .Build/bin/phpunit -c Build/php-unit/UnitTests.xml" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? ;; *) + loadHelp echo "Invalid -s option argument ${TEST_SUITE}" >&2 echo >&2 echo "${HELP}" >&2 exit 1 + ;; esac +cleanUp + +# Print summary +echo "" >&2 +echo "###########################################################################" >&2 +echo "Result of ${TEST_SUITE}" >&2 +echo "Container runtime: ${CONTAINER_BIN}" >&2 +if [[ ${IS_CORE_CI} -eq 1 ]]; then + echo "Environment: CI" >&2 +else + echo "Environment: local" >&2 +fi +echo "PHP: ${PHP_VERSION}" >&2 +echo "TYPO3: ${CORE_VERSION}" >&2 +if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then + echo "SUCCESS" >&2 +else + echo "FAILURE" >&2 +fi +echo "###########################################################################" >&2 +echo "" >&2 + +# Exit with code of test suite - This script return non-zero if the executed test failed. exit $SUITE_EXIT_CODE diff --git a/Build/php-cs-fixer/.php-cs-fixer.dist.php b/Build/php-cs-fixer/.php-cs-fixer.dist.php index d606407..ec82859 100644 --- a/Build/php-cs-fixer/.php-cs-fixer.dist.php +++ b/Build/php-cs-fixer/.php-cs-fixer.dist.php @@ -1,6 +1,22 @@ getRules(); +unset( + $rules['@PER'], + $rules['function_typehint_space'], + $rules['curly_braces_position'], +); +$config->setRules($rules); +$config->addRules([ + '@PER-CS' => true, + 'type_declaration_spaces' => [ + 'elements' => [ + 'function', + 'property', + ], + ], +]); $config ->getFinder() ->in(__DIR__) diff --git a/Build/php-unit/UnitTestsBootstrap.php b/Build/php-unit/UnitTestsBootstrap.php index 7ed7ee2..a628250 100644 --- a/Build/php-unit/UnitTestsBootstrap.php +++ b/Build/php-unit/UnitTestsBootstrap.php @@ -50,12 +50,12 @@ $cache = new \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend( 'core', - new \TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []) + new \TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []), ); // Set all packages to active $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, - \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) + \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache), ); \TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon new file mode 100644 index 0000000..fdb88ac --- /dev/null +++ b/Build/phpstan/phpstan-baseline.neon @@ -0,0 +1,46 @@ +parameters: + ignoreErrors: + - + message: "#^Call to sprintf contains 0 placeholders, 1 value given\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Call to static method getClassesInNamespace\\(\\) on an unknown class HaydenPierce\\\\ClassFinder\\\\ClassFinder\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Class T3docs\\\\Codesnippet\\\\Util\\\\Null_ not found\\.$#" + count: 17 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Static method T3docs\\\\Codesnippet\\\\Util\\\\ArrayHelper\\:\\:varExportArrayShort\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Variable \\$description might not be defined\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Variable \\$paramName might not be defined\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Variable \\$shortClass might not be defined\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Variable \\$type might not be defined\\.$#" + count: 1 + path: ../../Classes/Util/ClassDocsHelper.php + + - + message: "#^Call to an undefined static method T3docs\\\\Codesnippet\\\\Util\\\\ClassDocsHelper\\:\\:extractDocsFromClass\\(\\)\\.$#" + count: 1 + path: ../../Classes/Util/Typo3CodeSnippets.php diff --git a/Build/phpstan/phpstan-typo3-constants.php b/Build/phpstan/phpstan-typo3-constants.php new file mode 100644 index 0000000..5b59c2e --- /dev/null +++ b/Build/phpstan/phpstan-typo3-constants.php @@ -0,0 +1,18 @@ +withPaths([ + __DIR__ . '/../../Classes', + __DIR__ . '/../../Configuration', + __DIR__ . '/../../*.php', + ]) + ->withPhpSets() + ->withSets([ + Typo3SetList::CODE_QUALITY, + Typo3SetList::GENERAL, + Typo3LevelSetList::UP_TO_TYPO3_13, + ]) + ->withImportNames(importShortClasses: false, removeUnusedImports: true) + // To have a better analysis from PHPStan, we teach it here some more things + ->withPHPStanConfigs([ + Typo3Option::PHPSTAN_FOR_RECTOR_PATH, + ]) + ->withRules([ + AddVoidReturnTypeWhereNoReturnRector::class, + ConvertImplicitVariablesToExplicitGlobalsRector::class, + ]) + ->withConfiguredRule(ExtEmConfRector::class, [ + ExtEmConfRector::TYPO3_VERSION_CONSTRAINT => '13.1.0-13.99.99', + ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [], + ]) + // If you use importNames(), you should consider excluding some TYPO3 files. + ->withSkip([ + // AddLiteralSeparatorToNumberRector would make the exception codes more readable. + // But as they are just timestamps this is not needed/wanted. + AddLiteralSeparatorToNumberRector::class, + ]) +; diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml deleted file mode 100644 index 1243872..0000000 --- a/Build/testing-docker/docker-compose.yml +++ /dev/null @@ -1,147 +0,0 @@ -version: '2.3' -services: - cgl: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${ROOT_DIR}:${ROOT_DIR} - working_dir: ${ROOT_DIR} - extra_hosts: - - "host.docker.internal:host-gateway" - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - PHP_CS_FIXER_IGNORE_ENV=1 php -dxdebug.mode=off \ - .Build/bin/php-cs-fixer fix \ - -v \ - ${CGLCHECK_DRY_RUN} \ - --config=Build/php-cs-fixer/.php-cs-fixer.dist.php \ - --using-cache=no . - " - - composer_update: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${ROOT_DIR}:${ROOT_DIR} - working_dir: ${ROOT_DIR} - extra_hosts: - - "host.docker.internal:host-gateway" - environment: - COMPOSER_CACHE_DIR: ".Build/.cache/composer" - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - - if [ ${TYPO3_VERSION} == "11.5" ]; then - composer rem --dev "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge" --no-update - composer req --dev --no-update \ - typo3/cms-composer-installers:^3.0 - composer req typo3/cms-core:^11.5 --no-update - fi - if [ ${TYPO3_VERSION} == "12.0" ]; then - composer req --dev --no-update \ - "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge":^0.0.1 - composer req typo3/cms-core:~12.0@dev -W --no-update - fi - if [ ${TYPO3_VERSION} == "main" ]; then - composer req --dev --no-update \ - "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge":^0.0.1 \ - typo3/cms-composer-installers:^5.0 \ - typo3/cms-backend:dev-main \ - typo3/cms-recordlist:dev-main \ - typo3/cms-frontend:dev-main \ - typo3/cms-extbase:dev-main \ - typo3/cms-fluid:dev-main \ - typo3/cms-install:dev-main - composer req typo3/cms-core:dev-main -W --no-update - fi - - composer update --no-progress --no-interaction; - " - - composer_validate: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${ROOT_DIR}:${ROOT_DIR} - working_dir: ${ROOT_DIR} - extra_hosts: - - "host.docker.internal:host-gateway" - environment: - COMPOSER_CACHE_DIR: ".Build/.cache/composer" - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - composer validate --no-check-lock; - " - - composer_normalize: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${ROOT_DIR}:${ROOT_DIR} - working_dir: ${ROOT_DIR} - extra_hosts: - - "host.docker.internal:host-gateway" - environment: - COMPOSER_CACHE_DIR: ".Build/.cache/composer" - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - composer normalize --no-check-lock --no-update-lock ${COMPOSER_NORMALIZE_DRY_RUN}; - " - - lint: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${ROOT_DIR}:${ROOT_DIR} - working_dir: ${ROOT_DIR} - extra_hosts: - - "host.docker.internal:host-gateway" - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - find . -name \\*.php ! -path "./.Build/\\*" -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null - " - - unit: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${ROOT_DIR}:${ROOT_DIR} - working_dir: ${ROOT_DIR} - extra_hosts: - - "host.docker.internal:host-gateway" - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - .Build/bin/phpunit -c Build/php-unit/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; - else - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal\" \ - .Build/bin/phpunit -c Build/php-unit/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; - fi - " diff --git a/Classes/Command/PhpDomainCommand.php b/Classes/Command/PhpDomainCommand.php index e2d0b28..a2a1832 100644 --- a/Classes/Command/PhpDomainCommand.php +++ b/Classes/Command/PhpDomainCommand.php @@ -36,7 +36,7 @@ protected function configure() ->addArgument( 'config', InputArgument::OPTIONAL, - 'Enter the fully qualified name of the structure you want to export' + 'Enter the fully qualified name of the structure you want to export', ); } diff --git a/Classes/Exceptions/ClassNotPublicException.php b/Classes/Exceptions/ClassNotPublicException.php index 2603843..c8b4aca 100644 --- a/Classes/Exceptions/ClassNotPublicException.php +++ b/Classes/Exceptions/ClassNotPublicException.php @@ -17,6 +17,4 @@ namespace T3docs\Codesnippet\Exceptions; -class ClassNotPublicException extends \TYPO3\CMS\Core\Exception -{ -} +class ClassNotPublicException extends \TYPO3\CMS\Core\Exception {} diff --git a/Classes/Exceptions/InvalidConfigurationException.php b/Classes/Exceptions/InvalidConfigurationException.php index 879bd57..42594e7 100644 --- a/Classes/Exceptions/InvalidConfigurationException.php +++ b/Classes/Exceptions/InvalidConfigurationException.php @@ -17,6 +17,4 @@ namespace T3docs\Codesnippet\Exceptions; -class InvalidConfigurationException extends \TYPO3\CMS\Core\Exception -{ -} +class InvalidConfigurationException extends \TYPO3\CMS\Core\Exception {} diff --git a/Classes/Util/ArrayHelper.php b/Classes/Util/ArrayHelper.php index 2cb3e7b..75659bc 100644 --- a/Classes/Util/ArrayHelper.php +++ b/Classes/Util/ArrayHelper.php @@ -85,7 +85,7 @@ protected static function extractFieldFromArray(array $array, string $field): ar $path = str_getcsv($field, '/'); $pathReverse = array_reverse($path); - for ($i=0; $i < count($pathReverse); $i++) { + for ($i = 0; $i < count($pathReverse); $i++) { if ($i === 0) { $result = [$pathReverse[$i] => $value]; } else { diff --git a/Classes/Util/ClassDocsHelper.php b/Classes/Util/ClassDocsHelper.php index 47336fa..fbf42bd 100644 --- a/Classes/Util/ClassDocsHelper.php +++ b/Classes/Util/ClassDocsHelper.php @@ -71,12 +71,12 @@ public function __construct() } public static function extractPhpDomainAll( - array $config + array $config, ) { if (!$config['namespace']) { throw new InvalidConfigurationException('parameter namespace is required'); } - $classes = ClassFinder::getClassesInNamespace($config['namespace'], (int)($config['mode']) ?? 1); + $classes = ClassFinder::getClassesInNamespace($config['namespace'], (int) ($config['mode']) ?? 1); $path = $config['path'] ?? ''; if (str_ends_with($path, '/')) { @@ -97,7 +97,7 @@ public static function extractPhpDomainAll( } $classPartArray = explode('\\', $class); if (count($classPartArray) > 1) { - $shortClass = $classPartArray[count($classPartArray) -1]; + $shortClass = $classPartArray[count($classPartArray) - 1]; } $extractPhpDomainConfig = [ 'class' => $class, @@ -117,7 +117,7 @@ public static function extractPhpDomainAll( .. include:: /CodeSnippets/%s.rst.txt ', RstHelper::escapeRst($shortClass), - $outputPath + $outputPath, ); try { @@ -126,7 +126,7 @@ public static function extractPhpDomainAll( $extractPhpDomainConfig, $content, $rstContent, - $config['overwriteRst'] ?? false + $config['overwriteRst'] ?? false, ); // only add Index.rst to directory if there was a rstfile written $rstDir = str_replace($config['namespace'], '', $class); @@ -200,7 +200,7 @@ public static function extractPhpDomainAll( RstHelper::escapeRst($addedNameSpaceConfig['short']), $config['namespace'] . '\\' . $addedNameSpace, $config['namespace'] . '\\' . $addedNameSpace, - $tree + $tree, ); $indexPart = str_replace($config['namespace'], '', $addedNameSpace); $indexPart = $config['path'] . str_replace('\\', '/', $indexPart); @@ -268,7 +268,7 @@ public static function extractPhpDomainAll( * @throws ClassNotPublicException */ public static function extractPhpDomain( - array $config + array $config, ): string { $class = $config['class']; $members = $config['members'] ?? []; @@ -325,7 +325,7 @@ public static function extractPhpDomain( $allowDeprecated, $includeConstructor, $gitHubLink, - $noindexInClassMembers + $noindexInClassMembers, ); } elseif ($classReflection->hasProperty($member)) { $result['properties'][] = self::getPropertyCode( @@ -333,7 +333,7 @@ public static function extractPhpDomain( $member, $withCode, $modifierSum, - $noindexInClassMembers + $noindexInClassMembers, ); } elseif ($classReflection->hasConstant($member)) { $result['constants'][] = self::getConstantCode( @@ -341,15 +341,15 @@ public static function extractPhpDomain( $member, $withCode, $modifierSum, - $noindexInClassMembers + $noindexInClassMembers, ); } else { throw new \ReflectionException( sprintf( 'Cannot extract constant nor property nor method "%s" from class "%s"', $member, - $class - ) + $class, + ), ); } } @@ -364,7 +364,7 @@ public static function extractPhpDomain( $allowDeprecated, $includeConstructor, $gitHubLink, - $noindexInClassMembers + $noindexInClassMembers, ); } foreach ($classReflection->getProperties() as $property) { @@ -373,7 +373,7 @@ public static function extractPhpDomain( $property->getName(), $withCode, $modifierSum, - $noindexInClassMembers + $noindexInClassMembers, ); } foreach ($classReflection->getConstants() as $constant => $constantValue) { @@ -395,7 +395,7 @@ public static function extractPhpDomain( $content = sprintf( $template, $classReflection->getName(), - $classSignature . $classBody + $classSignature . $classBody, ); } return $content; @@ -437,7 +437,7 @@ public static function getUseStatements(string $class): string $startLineBody = $classReflection->getStartLine(); $result = []; - for ($lineNumber=0; $lineNumber <= $startLineBody; $lineNumber++) { + for ($lineNumber = 0; $lineNumber <= $startLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); $line = $splFileObject->current(); if (preg_match('#^use [^;]*;#', $line) === 1) { @@ -490,11 +490,10 @@ public static function getClassSignature( string $class, bool $withCode, \ReflectionClass $reflectionClass, - $gitHubLink='', - $includeClassComment=true, + $gitHubLink = '', + $includeClassComment = true, $noindexInClass = false, - ): string - { + ): string { $classReflection = self::getClassReflection($class); $docBlockFactory = self::getDocBlockFactory(); @@ -531,11 +530,11 @@ public static function getClassSignature( } else { $result[] = sprintf('.. php:class:: %s', $classShortName); } - if($noindexInClass) { - $result[] = "\n".' :noindex:'; + if ($noindexInClass) { + $result[] = "\n" . ' :noindex:'; } if ($reflectionClass->isAbstract() && !$reflectionClass->isInterface()) { - $result[] = "\n".' :abstract:'; + $result[] = "\n" . ' :abstract:'; } $result[] = "\n\n"; if ($comment) { @@ -606,7 +605,7 @@ public static function getMethodCode( (!$allowInternal && $isInternal) or (!$allowDeprecated && $methodReflection->isDeprecated()) or (($modifierSum & $methodReflection->getModifiers()) == 0) - or (!$includeConstructor && $method=='__construct') + or (!$includeConstructor && $method == '__construct') ) { return ''; } @@ -621,9 +620,9 @@ public static function getMethodCode( $endLineBody = $methodReflection->getEndLine(); $startLineSignature = max($startLineBody - 20, 0); - for ($lineNumber=$startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { + for ($lineNumber = $startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); - if (strpos($splFileObject->current(), sprintf('function %s', RstHelper::escapeRst($method))) !== false) { + if (str_contains($splFileObject->current(), sprintf('function %s', RstHelper::escapeRst($method)))) { $startLineSignature = $lineNumber; } } @@ -728,7 +727,7 @@ public static function getMethodCode( } $codeResult = []; if ($withCode) { - for ($lineNumber=$startLineSignature; $lineNumber < $endLineBody; $lineNumber++) { + for ($lineNumber = $startLineSignature; $lineNumber < $endLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); $codeResult[] = $splFileObject->current(); } @@ -746,12 +745,12 @@ public static function getMethodCode( if ($startLineSignature) { $comment .= sprintf( 'See source code on `GitHub <%s>`__.', - $gitHubLink . '#L' . $startLineSignature + $gitHubLink . '#L' . $startLineSignature, ); } else { $comment .= sprintf( 'See source code on `GitHub <%s>`__.', - $gitHubLink + $gitHubLink, ); } } @@ -944,7 +943,7 @@ public static function getConstantCode( $line = $splFileObject->fgets(); if (preg_match(sprintf( '#const[\s]*%s\s*=\s*[^;]*;#', - $constant + $constant, ), $line) === 1) { $code[] = $line; break; diff --git a/Classes/Util/ClassHelper.php b/Classes/Util/ClassHelper.php index f227ed9..8602080 100644 --- a/Classes/Util/ClassHelper.php +++ b/Classes/Util/ClassHelper.php @@ -100,8 +100,8 @@ public static function extractMembersFromClass(array $config): string sprintf( 'Cannot extract constant nor property nor method "%s" from class "%s"', $member, - $class - ) + $class, + ), ); } } @@ -159,7 +159,7 @@ public static function getUseStatements(string $class): string $startLineBody = $classReflection->getStartLine(); $result = []; - for ($lineNumber=0; $lineNumber <= $startLineBody; $lineNumber++) { + for ($lineNumber = 0; $lineNumber <= $startLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); $line = $splFileObject->current(); if (preg_match('#^use [^;]*;#', $line) === 1) { @@ -219,9 +219,9 @@ public static function getClassSignature(string $class, bool $withComment = fals $endLineBody = $classReflection->getEndLine(); $startLineSignature = max($startLineBody - 20, 0); - for ($lineNumber=$startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { + for ($lineNumber = $startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); - if (strpos($splFileObject->current(), sprintf('class %s', $classShortName)) !== false) { + if (str_contains($splFileObject->current(), sprintf('class %s', $classShortName))) { $startLineSignature = $lineNumber; } } @@ -230,12 +230,12 @@ public static function getClassSignature(string $class, bool $withComment = fals if ($withComment && $classReflection->getDocComment() !== false) { $result[] = self::fixDocCommentIndentation($classReflection->getDocComment()) . "\n"; } - for ($lineNumber=$startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { + for ($lineNumber = $startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); $result[] = $splFileObject->current(); } $result[] = '%s' . "\n"; - $splFileObject->seek($endLineBody-1); + $splFileObject->seek($endLineBody - 1); $result[] = $splFileObject->current(); // SplFileObject locks the file, so null it when no longer needed @@ -292,9 +292,9 @@ public static function getMethodCode(string $class, string $method, bool $withCo $endLineBody = $methodReflection->getEndLine(); $startLineSignature = max($startLineBody - 20, 0); - for ($lineNumber=$startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { + for ($lineNumber = $startLineSignature; $lineNumber <= $startLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); - if (strpos($splFileObject->current(), sprintf('function %s', $method)) !== false) { + if (str_contains($splFileObject->current(), sprintf('function %s', $method))) { $startLineSignature = $lineNumber; } } @@ -303,7 +303,7 @@ public static function getMethodCode(string $class, string $method, bool $withCo if ($withComment && $methodReflection->getDocComment() !== false) { $result[] = self::fixDocCommentIndentation($methodReflection->getDocComment()) . "\n"; } - for ($lineNumber=$startLineSignature; $lineNumber < $endLineBody; $lineNumber++) { + for ($lineNumber = $startLineSignature; $lineNumber < $endLineBody; $lineNumber++) { $splFileObject->seek($lineNumber); $result[] = $splFileObject->current(); } diff --git a/Classes/Util/CodeSnippetCreator.php b/Classes/Util/CodeSnippetCreator.php index a7d4633..a363179 100644 --- a/Classes/Util/CodeSnippetCreator.php +++ b/Classes/Util/CodeSnippetCreator.php @@ -30,8 +30,8 @@ class CodeSnippetCreator { - const RECURSIVE_PATH = 1; - const FLAT_PATH = 1; + public const RECURSIVE_PATH = 1; + public const FLAT_PATH = 1; private static $fileCount = 0; private static $configPath = ''; @@ -87,7 +87,7 @@ public static function writeSimpleFile($content, $path) mkdir(dirname($filename), 0755, true); \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile( $filename, - $content + $content, ); } @@ -107,7 +107,7 @@ public static function writeFile(array $entry, String $content, String $rstConte $content = preg_replace("/\n\s+\n/", "\n\n", $content); \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile( $filename, - $content + $content, ); if ($rstContent && $entry['rstFileName']) { @@ -115,7 +115,7 @@ public static function writeFile(array $entry, String $content, String $rstConte mkdir(dirname($rstFilename), 0755, true); \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile( $filename, - $content + $content, ); if ($overwriteRst || !file_exists($rstFilename)) { \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($rstFilename, $rstContent); diff --git a/Classes/Util/FileHelper.php b/Classes/Util/FileHelper.php index 24aa5e5..f261f41 100644 --- a/Classes/Util/FileHelper.php +++ b/Classes/Util/FileHelper.php @@ -57,7 +57,7 @@ public static function getAbsoluteTypo3Path(string $relativePath): string public static function getAbsoluteDocumentationPath(string $relativePath): string { return FileHelper::getPathBySegments( - $relativePath + $relativePath, ); } @@ -137,7 +137,7 @@ public static function deleteRecursively(string $path): void */ public static function getRealPath(string $path): string { - return strpos($path, 'vfs://') === 0 ? $path : realpath($path); + return str_starts_with($path, 'vfs://') ? $path : realpath($path); } /** @@ -172,7 +172,7 @@ public static function getPathBySegments(string ...$segments): string public static function isAbsolutePath(string $path): bool { - return strpos($path, '://') !== false || strpos($path, DIRECTORY_SEPARATOR) === 0; + return str_contains($path, '://') || str_starts_with($path, DIRECTORY_SEPARATOR); } /** @@ -207,6 +207,6 @@ public static function getUrlBySegments(string ...$segments): string public static function isAbsoluteUrl(string $url): bool { - return strpos($url, '://') !== false || strpos($url, '/') === 0; + return str_contains($url, '://') || str_starts_with($url, '/'); } } diff --git a/Classes/Util/JsonHelper.php b/Classes/Util/JsonHelper.php index 19e4f3d..826aa7e 100644 --- a/Classes/Util/JsonHelper.php +++ b/Classes/Util/JsonHelper.php @@ -78,7 +78,7 @@ protected static function grabInlineJsonsAndReplaceWithPlaceholders( &$jsonEntry, int $inlineLevel, array &$inlineJsons, - array $keys = [] + array $keys = [], ): void { if (!is_array($jsonEntry)) { return; @@ -88,9 +88,9 @@ protected static function grabInlineJsonsAndReplaceWithPlaceholders( foreach ($jsonEntry as $key => &$child) { self::grabInlineJsonsAndReplaceWithPlaceholders( $child, - $inlineLevel-1, + $inlineLevel - 1, $inlineJsons, - array_merge($keys, [$key]) + array_merge($keys, [$key]), ); } } else { diff --git a/Classes/Util/MathHelper.php b/Classes/Util/MathHelper.php index 8d72424..065bb56 100644 --- a/Classes/Util/MathHelper.php +++ b/Classes/Util/MathHelper.php @@ -52,7 +52,7 @@ public static function getRectangleInRectangle( int $width, int $height, int $outerWidth, - int $outerHeight + int $outerHeight, ): array { $x = $x < 0 ? $outerWidth - -$x : $x; $y = $y < 0 ? $outerHeight - -$y : $y; diff --git a/Classes/Util/Typo3CodeSnippets.php b/Classes/Util/Typo3CodeSnippets.php index 9bec151..fd6d3c7 100644 --- a/Classes/Util/Typo3CodeSnippets.php +++ b/Classes/Util/Typo3CodeSnippets.php @@ -55,7 +55,7 @@ public function createPhpArrayCodeSnippetFromConfig(array $config): string $params['name'], $params['showLineNumbers'], $params['lineStartNumber'], - $params['emphasizeLines'] + $params['emphasizeLines'], ); } @@ -77,14 +77,14 @@ public function createCodeSnippetFromConfig(array $config): string * Reads a TYPO3 PHP file and generates a reST file from it for inclusion. */ public function createCodeSnippet( - array $config + array $config, ): string { $config['language'] = $config['language'] !== '' ? $config['language'] : $this->getCodeLanguageByFileExtension($config['sourceFile']); $config['relativeSourcePath'] = FileHelper::getRelativeSourcePath($config['sourceFile']); $config['absoluteSourcePath'] = FileHelper::getAbsoluteTypo3Path($config['relativeSourcePath']); $config['code'] = $this->processCode( $this->read($config['absoluteSourcePath']), - $config + $config, ); $config['sourceHint'] = $config['relativeSourcePath']; @@ -116,8 +116,8 @@ private function shortenFirstPhpComment(string $code): string '/* * This file is part of the TYPO3 CMS project. [...] */ ', - $code - )??$code; + $code, + ) ?? $code; } /** @@ -139,14 +139,14 @@ private function shortenFirstPhpComment(string $code): string * @param int[] $emphasizeLines Emphasize particular lines of the code block */ public function createJsonCodeSnippet( - array $config + array $config, ): string { $absoluteSourcePath = FileHelper::getAbsoluteTypo3Path($config['sourceFile']); $code = $this->readJson( $absoluteSourcePath, $config['fields'], - $config['inlineLevel'] + $config['inlineLevel'], ); $config['language'] = 'json'; $config['code'] = $code; @@ -181,7 +181,7 @@ public function createPhpArrayCodeSnippet( string $name = '', bool $showLineNumbers = false, int $lineStartNumber = 0, - array $emphasizeLines = [] + array $emphasizeLines = [], ): string { $relativeTargetPath = FileHelper::getRelativeTargetPath($targetFileName); $relativeSourcePath = FileHelper::getRelativeSourcePath($sourceFile); @@ -212,7 +212,7 @@ public function createPhpArrayCodeSnippet( * $config['withComment'] Include comments? */ public function createPhpClassCodeSnippet( - array $config + array $config, ): string { $config['code'] = $this->readPhpClass($config); @@ -245,7 +245,7 @@ public function createPhpClassDocs( array $allowedModifiers = ['public'], bool $allowInternal = false, bool $allowDeprecated = false, - bool $includeConstructor = false + bool $includeConstructor = false, ): void { $relativeTargetPath = FileHelper::getRelativeTargetPath($targetFileName); $absoluteTargetPath = FileHelper::getAbsoluteDocumentationPath($relativeTargetPath); @@ -257,12 +257,12 @@ public function createPhpClassDocs( $allowedModifiers, $allowInternal, $allowDeprecated, - $includeConstructor + $includeConstructor, ); $this->writeRst( $absoluteTargetPath, $class, - $code + $code, ); } @@ -291,7 +291,7 @@ public function createXmlCodeSnippet( string $name = '', bool $showLineNumbers = false, int $lineStartNumber = 0, - array $emphasizeLines = [] + array $emphasizeLines = [], ): string { $relativeSourcePath = FileHelper::getRelativeSourcePath($sourceFile); $absoluteSourcePath = FileHelper::getAbsoluteTypo3Path($relativeSourcePath); @@ -338,7 +338,7 @@ public function createYamlCodeSnippet( string $name = '', bool $showLineNumbers = false, int $lineStartNumber = 0, - array $emphasizeLines = [] + array $emphasizeLines = [], ): string { $relativeTargetPath = FileHelper::getRelativeTargetPath($targetFileName); $absoluteTargetPath = FileHelper::getAbsoluteDocumentationPath($relativeTargetPath); @@ -395,9 +395,9 @@ protected function getCodeLanguageByFileExtension(string $filePath): string 'The programming language of the file "%s" cannot be determined automatically via the ' . 'file extension "%s". Please specify the language explicitly.', $filePath, - $fileExtension + $fileExtension, ), - 4001 + 4001, ); } @@ -416,7 +416,7 @@ protected function read(string $path): string protected function readJson( string $path, array $fields, - int $inlineLevel + int $inlineLevel, ): string { $json = file_get_contents($path); $code = JsonHelper::extractFieldsFromJson($json, $fields, $inlineLevel); @@ -452,7 +452,7 @@ protected function transformPhpToDocs( array $allowedModifiers, bool $allowInternal, bool $allowDeprecated, - bool $includeConstructor + bool $includeConstructor, ): string { return ClassDocsHelper::extractDocsFromClass( $class, @@ -461,7 +461,7 @@ protected function transformPhpToDocs( $allowedModifiers, $allowInternal, $allowDeprecated, - $includeConstructor + $includeConstructor, ); } @@ -480,7 +480,7 @@ protected function readXml(string $path, array $nodes): string protected function readYaml( string $path, array $fields, - int $inlineLevel + int $inlineLevel, ): string { $yaml = file_get_contents($path); $code = YamlHelper::extractFieldsFromYaml($yaml, $fields, $inlineLevel); @@ -490,7 +490,7 @@ protected function readYaml( protected function writeRst( string $targetPath, string $sourceHint, - string $content + string $content, ): void { $rst = <<<'NOWDOC' .. ========================================================= @@ -510,7 +510,7 @@ protected function writeRst( } protected function getCodeBlockRst( - array $config + array $config, ): string { $options = []; if (isset($config['caption']) && $config['caption'] !== '') { @@ -525,26 +525,26 @@ protected function getCodeBlockRst( if (isset($config['lineStartNumber']) && $config['lineStartNumber'] > 0) { $options[] = sprintf( ':lineno-start: %s', - $config['lineStartNumber'] + $config['lineStartNumber'], ); } if (isset($config['emphasizeLines']) && count($config['emphasizeLines']) > 0) { $options[] = sprintf( ':emphasize-lines: %s', - implode(',', $config['emphasizeLines']) + implode(',', $config['emphasizeLines']), ); } if (count($options) > 0) { $options = StringHelper::indentMultilineText(implode( "\n", - $options + $options, ), ' ') . "\n"; } else { $options = ''; } $codeBlockContent = StringHelper::indentMultilineText( $config['code'], - ' ' + ' ', ); $rst = <<<'NOWDOC' @@ -560,7 +560,7 @@ protected function getCodeBlockRst( $config['sourceHint'] ?? '', $config['language'] ?? 'none', $options, - $codeBlockContent + $codeBlockContent, ); return $rst; diff --git a/Classes/Util/XmlHelper.php b/Classes/Util/XmlHelper.php index 4f3b663..1b55ccb 100644 --- a/Classes/Util/XmlHelper.php +++ b/Classes/Util/XmlHelper.php @@ -93,9 +93,9 @@ public static function extractNodesFromXml(string $xml, array $xPaths): string self::stopCollectingXmlErrors(); throw new \Exception( self::printError( - sprintf('XPath "%s" does not match any XML nodes.', $xPath) + sprintf('XPath "%s" does not match any XML nodes.', $xPath), ), - 4003 + 4003, ); } } else { diff --git a/Classes/Utility/PhpDocToRstUtility.php b/Classes/Utility/PhpDocToRstUtility.php index 1377632..95702fa 100644 --- a/Classes/Utility/PhpDocToRstUtility.php +++ b/Classes/Utility/PhpDocToRstUtility.php @@ -47,7 +47,7 @@ function (array $matches): string { $code = StringHelper::indentMultilineText($matches[2], ' '); return '.. code-block:: php' . LF . $code; }, - $comment + $comment, ); return $comment; } diff --git a/Tests/Unit/Service/TestClasses/MyFirstClass.php b/Tests/Unit/Service/TestClasses/MyFirstClass.php index f910ed0..7b1d7e9 100644 --- a/Tests/Unit/Service/TestClasses/MyFirstClass.php +++ b/Tests/Unit/Service/TestClasses/MyFirstClass.php @@ -17,7 +17,5 @@ class MyFirstClass { - public function __construct() - { - } + public function __construct() {} } diff --git a/Tests/Unit/Service/TestClasses/MySecondClass.php b/Tests/Unit/Service/TestClasses/MySecondClass.php index 09f552f..3c1d171 100644 --- a/Tests/Unit/Service/TestClasses/MySecondClass.php +++ b/Tests/Unit/Service/TestClasses/MySecondClass.php @@ -17,7 +17,5 @@ class MySecondClass { - public function __construct() - { - } + public function __construct() {} } diff --git a/Tests/Unit/Service/TestClasses/TestClass1.php b/Tests/Unit/Service/TestClasses/TestClass1.php index e39c7c4..897585b 100644 --- a/Tests/Unit/Service/TestClasses/TestClass1.php +++ b/Tests/Unit/Service/TestClasses/TestClass1.php @@ -28,7 +28,7 @@ public function myMethod(): string public function createMyFirstObject( array $options, - int $limit = 0 + int $limit = 0, ): MyFirstClass { return new MyFirstClass(); } diff --git a/composer.json b/composer.json index 94149c8..1bc80fb 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "t3docs/codesnippet", "description": "This extension packages creates restructured files to document the API", "license": [ - "GPL-2.0+" + "GPL-2.0-or-later" ], "type": "typo3-cms-extension", "authors": [ @@ -14,12 +14,13 @@ "require": { "php": "^8.1", "symfony/console": "^6.4 || ^7.0", - "typo3/cms-core": "^12.4 || ^13.0 || dev-main" + "typo3/cms-core": "^12.4 || ^13.0 || ^13.1 || dev-main" }, "require-dev": { "ergebnis/composer-normalize": "^2.42", - "typo3/coding-standards": "^0.7.1", - "typo3/testing-framework": "^8.0" + "phpstan/phpstan": "^1.10", + "typo3/coding-standards": "dev-main", + "typo3/testing-framework": "^8.0.9" }, "autoload": { "psr-4": { @@ -33,10 +34,10 @@ }, "config": { "allow-plugins": { - "typo3/cms-composer-installers": true, - "typo3/class-alias-loader": true, "ergebnis/composer-normalize": true, - "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge": true + "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge": true, + "typo3/class-alias-loader": true, + "typo3/cms-composer-installers": true }, "bin-dir": ".Build/bin", "sort-packages": true,