From a107b7f1f79af12e0b09f70be47b68e3f69b4504 Mon Sep 17 00:00:00 2001
From: Jonathan Smith <20701-jonathan1055@users.noreply.drupalcode.org>
Date: Wed, 18 Dec 2024 09:29:49 +0000
Subject: [PATCH] Issue #3397162 by jonathan1055, alexpott, fjgarlin, cmlara,
 simonbaese: Tweak PHPStan config so paths are always correct and baseline is
 more usable

---
 assets/phpstan.neon                     |  3 +-
 docs/jobs/phpstan.md                    | 12 ++++--
 includes/include.drupalci.main.yml      | 49 ++++++++++++++-----------
 includes/include.drupalci.variables.yml |  4 ++
 4 files changed, 43 insertions(+), 25 deletions(-)

diff --git a/assets/phpstan.neon b/assets/phpstan.neon
index faf3dd1c..8a55bc46 100644
--- a/assets/phpstan.neon
+++ b/assets/phpstan.neon
@@ -1,5 +1,6 @@
 includes:
-  - phpstan-baseline.neon
+  # This placeholder filename will be replaced with the value of variable $_PHPSTAN_BASELINE_FILENAME
+  - BASELINE_PLACEHOLDER
 parameters:
     level: 0
     fileExtensions:
diff --git a/docs/jobs/phpstan.md b/docs/jobs/phpstan.md
index afb1c20a..acf09bda 100644
--- a/docs/jobs/phpstan.md
+++ b/docs/jobs/phpstan.md
@@ -6,14 +6,20 @@ If the project contains a `phpstan.neon` configuration file it will be used. If
 
 The pipeline variable `_PHPSTAN_LEVEL` can be set to a value from 0-9 to specify how relaxed or severe the code quality checks should be. This can be used to temporarily override the value in your `phpstan.neon` file. If no value is given, either in the variable or the file, then the default of 0 is used. See the [PHPStan rule levels](https://phpstan.org/user-guide/rule-levels) for more information.
 
-Projects can specify [baseline](https://phpstan.org/user-guide/baseline) file(s) of messages to ignore, using the `includes:` keyword in the `phpstan.neon` file:
+Projects can optionally specify [baseline](https://phpstan.org/user-guide/baseline) file(s) of messages to ignore. The default baseline file `phpstan-baseline.neon` stored in the project top-level folder, will be included if using the default `phpstan.neon` config file. If your project's baseline file is called something different, then specify the name in the `_PHPSTAN_BASELINE_FILENAME` variable:
+```
+variables:
+  _PHPSTAN_BASELINE_FILENAME: 'your-baseline-file.neon'
+```
+
+To include multiple baseline files, your project will need its own `phpstan.neon` config file with a `includes:` keyword, for example:
 ```
 includes:
-  - your-baseline-file.neon
+  - phpstan-baseline.neon
   - second-baseline-file.neon
 ```
 
-Any warnings that are found are written to a new baseline file which is available as a job artifact for download. These can be added into the existing baseline file until the code is fixed. Unmatched messages in the baseline file are also reported in the log.
+A new baseline file is created which is available as a job artifact for download. This file will contain all warnings found, including those ignored in the single baseline file named in `_PHPSTAN_BASELINE_FILENAME`. Unmatched messages in the baseline file are reported in the log.
 
 In addition to testing at the current core version, the `phpstan` job has optional variants for the next major and minor core versions and the maximum PHP version.
 
diff --git a/includes/include.drupalci.main.yml b/includes/include.drupalci.main.yml
index f05176c5..0c87b7ce 100644
--- a/includes/include.drupalci.main.yml
+++ b/includes/include.drupalci.main.yml
@@ -634,34 +634,41 @@ phpcs:
     - composer
   script:
     - *check-composer-end-code
-    # If present, use PHPStan configuration neon file.
+    # Run from within project directory so paths are correct.
+    - cd $CI_PROJECT_DIR/$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME && pwd
+    # If there is no PHPStan configuration neon file get the default from /assets/phpstan.neon
     - |
-      if [ -f $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/phpstan.neon ]; then
-        echo "Using the existing phpstan.neon configuration from the project."
+      if [ ! -f phpstan.neon ]; then
+        echo "Getting default phpstan.neon from https://git.drupalcode.org/$_CURL_TEMPLATES_REPO/-/raw/$_CURL_TEMPLATES_REF/assets/phpstan.neon"
+        curl -OL https://git.drupalcode.org/$_CURL_TEMPLATES_REPO/-/raw/$_CURL_TEMPLATES_REF/assets/phpstan.neon
+        # Change the baseline filename placeholder to the runtime value in $_PHPSTAN_BASELINE_FILENAME
+        sed -i "s#BASELINE_PLACEHOLDER#$_PHPSTAN_BASELINE_FILENAME#g" phpstan.neon
       else
-        echo "Executing curl -L --output $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/phpstan.neon https://git.drupalcode.org/$_CURL_TEMPLATES_REPO/-/raw/$_CURL_TEMPLATES_REF/assets/phpstan.neon"
-        curl -L --output $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/phpstan.neon https://git.drupalcode.org/$_CURL_TEMPLATES_REPO/-/raw/$_CURL_TEMPLATES_REF/assets/phpstan.neon
+        echo "Using the project's existing phpstan.neon config file"
       fi
-      export PHPSTAN_CONFIGURATION="--configuration=$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/phpstan.neon"
-      [ -n "$_PHPSTAN_LEVEL" ] && export PHPSTAN_CONFIGURATION="$PHPSTAN_CONFIGURATION --level=$_PHPSTAN_LEVEL"
-    # Add an empty baseline file to ensure it exists.
-    - touch $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/phpstan-baseline.neon
-    - php vendor/bin/phpstan --version
+    # Create an empty baseline if the file does not exist, as this is referenced in the default config file.
+    - touch $_PHPSTAN_BASELINE_FILENAME
+    - test -n "$_PHPSTAN_LEVEL" && LEVEL="--level=$_PHPSTAN_LEVEL"
+    - php $CI_PROJECT_DIR/vendor/bin/phpstan --version
     - php --version
     # Rely on PHPStan caching to execute analysis multiple times without performance drawback.
     # Output a copy in junit.
-    - echo "Executing php vendor/bin/phpstan analyze $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME $PHPSTAN_CONFIGURATION"
-    - php vendor/bin/phpstan analyze $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME $PHPSTAN_CONFIGURATION --error-format=junit > junit.xml || true
+    - echo "Executing php $CI_PROJECT_DIR/vendor/bin/phpstan analyze . --autoload-file='$CI_PROJECT_DIR/vendor/autoload.php' $LEVEL"
+    - php $CI_PROJECT_DIR/vendor/bin/phpstan analyze . --autoload-file="$CI_PROJECT_DIR/vendor/autoload.php" $LEVEL --no-progress --error-format=junit > $CI_PROJECT_DIR/junit.xml || true
     # Output a copy in GitLab code quality format.
-    - php vendor/bin/phpstan analyze $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME $PHPSTAN_CONFIGURATION --no-progress --error-format=gitlab > phpstan-quality-report.json || true
+    - php $CI_PROJECT_DIR/vendor/bin/phpstan analyze . --autoload-file="$CI_PROJECT_DIR/vendor/autoload.php" $LEVEL --no-progress --error-format=gitlab > $CI_PROJECT_DIR/phpstan-quality-report.json || true
     # Output a copy in plain text for human logs.
-    - php vendor/bin/phpstan analyze $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME $PHPSTAN_CONFIGURATION --no-progress || EXIT_CODE=$?
-    # Generate a new baseline. Allow empty baseline to ensure that artifact exists.
-    - |
-      php vendor/bin/phpstan analyze $_WEB_ROOT/modules/custom/$CI_PROJECT_NAME $PHPSTAN_CONFIGURATION --no-progress --generate-baseline --allow-empty-baseline || true
-      echo "This baseline file is an artifact you can download and use in the project."
-    # Ensure paths in artifacts are git-relative.
-    - sed -i "s#$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/##" junit.xml phpstan-baseline.neon phpstan-quality-report.json || true
+    - php $CI_PROJECT_DIR/vendor/bin/phpstan analyze . --autoload-file="$CI_PROJECT_DIR/vendor/autoload.php" $LEVEL --no-progress || EXIT_CODE=$?
+    # Delete the phpstan baseline file so that the new one will contain all errors ignored in that baseline file.
+    # This has to be delete (not just erase the contents) so that the symlink is broken, which will allow the file to be moved afterwards.
+    - rm -v $_PHPSTAN_BASELINE_FILENAME
+    - echo "Generating a PHPStan baseline file '$_PHPSTAN_BASELINE_FILENAME' available as a job artifact to download. This file will contain any new errors reported above, plus those already ignored in '$_PHPSTAN_BASELINE_FILENAME' if that file is being used."
+    # The baseline is created in the current directory so that the paths are correctly relative. Then the file is moved to the top level folder.
+    - php $CI_PROJECT_DIR/vendor/bin/phpstan analyze . --autoload-file="$CI_PROJECT_DIR/vendor/autoload.php" $LEVEL --no-progress --generate-baseline=$_PHPSTAN_BASELINE_FILENAME --allow-empty-baseline || true
+    - mv -v $_PHPSTAN_BASELINE_FILENAME $CI_PROJECT_DIR
+    # Fix paths in message text in the reports.
+    - cd $CI_PROJECT_DIR && pwd
+    - sed -i "s#$CI_PROJECT_DIR/$_WEB_ROOT/modules/custom/$CI_PROJECT_NAME/##g" junit.xml $_PHPSTAN_BASELINE_FILENAME phpstan-quality-report.json
     - exit $EXIT_CODE
   allow_failure: true
   artifacts:
@@ -674,7 +681,7 @@ phpcs:
     name: artifacts-$CI_PIPELINE_ID-$CI_JOB_NAME_SLUG
     paths:
       - junit.xml
-      - phpstan-baseline.neon
+      - $_PHPSTAN_BASELINE_FILENAME
       - phpstan-quality-report.json
 
 phpstan:
diff --git a/includes/include.drupalci.variables.yml b/includes/include.drupalci.variables.yml
index 183e04cc..01992952 100644
--- a/includes/include.drupalci.variables.yml
+++ b/includes/include.drupalci.variables.yml
@@ -140,6 +140,10 @@ variables:
     value: ''
     description: 'Specify the PHPStan level to use (0-9). If left blank it will use the `level` value in your phpstan.neon configuration file, or default to 0 if there is no `level` parameter. See https://phpstan.org/user-guide/rule-levels for more information.'
 
+  _PHPSTAN_BASELINE_FILENAME:
+    value: 'phpstan-baseline.neon'
+    description: 'The PHPStan baseline file of warnings to ignore. The default is `phpstan-baseline.neon` but a project can have a custom filename if required.'
+
   _SHOW_ENVIRONMENT_VARIABLES:
     value: '0'
     description: 'Set to 1 to show all the environment variables in the Composer and PhpUnit jobs. Known variables with Personal Identifiable Information will still be hidden. The default is 0 for no output at all.'
-- 
GitLab