From 60b01629844214cbf3dfd2ccdb171c1852d099ec Mon Sep 17 00:00:00 2001 From: olatechpro Date: Mon, 18 May 2026 11:16:36 +0100 Subject: [PATCH 1/6] Add reusable code complexity workflow #8 --- .github/workflows/code-complexity.yml | 44 +++++++++++++++++++++++++++ README.md | 20 ++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 .github/workflows/code-complexity.yml diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml new file mode 100644 index 0000000..673050c --- /dev/null +++ b/.github/workflows/code-complexity.yml @@ -0,0 +1,44 @@ +name: Analyze code complexity +permissions: + contents: read +on: + workflow_call: + +env: + DEV_SCRIPTS_DIR: ${{ github.workspace }}/vendor/publishpress/dev-workspace/scripts + +jobs: + check: + name: Run code complexity analysis + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout repository + # actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Set up PHP + # shivammathur/setup-php@2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f + with: + php-version: 8.3 + extensions: mbstring,xml,curl,zip,intl,bcmath,gettext,mysqli,phar,gd,iconv,yaml + tools: composer:v2 + + - name: Create root .env file + run: cp $GITHUB_WORKSPACE/.env.example $GITHUB_WORKSPACE/.env + + - name: Validate Composer configuration + run: composer validate --strict + + - name: Install Composer dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Show dev-workspace tool versions + run: composer info:version + + - name: Add dev-workspace scripts to PATH + run: echo "$DEV_SCRIPTS_DIR" >> "$GITHUB_PATH" + + - name: Run code complexity analysis + run: composer metrics \ No newline at end of file diff --git a/README.md b/README.md index b13b708..51bb247 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Reusable GitHub Actions workflows for PublishPress plugin repositories. - `.github/workflows/unit-tests.yml`: Runs PHPUnit tests. - `.github/workflows/code-standards.yml`: Runs PHP compatibility and lint checks. +- `.github/workflows/code-complexity.yml`: Runs PHP code complexity analysis with PHPMetrics. - `.github/workflows/deploy-free.yml`: Builds and deploys free plugin releases to WordPress.org and uploads release assets to GitHub. - `.github/workflows/deploy-free-assets.yml`: Updates WordPress.org plugin assets/readme. - `.github/workflows/deploy-pro.yml`: Builds pro plugin packages and uploads release assets to GitHub. @@ -54,6 +55,25 @@ jobs: uses: publishpress/github-workflows/.github/workflows/code-check.yml@ ``` +### Code complexity example + +```yaml +name: Code Complexity + +on: + pull_request: + branches: [ master, development ] + push: + branches: [ master, development ] + +permissions: + contents: read + +jobs: + code_complexity: + uses: publishpress/github-workflows/.github/workflows/code-complexity.yml@ +``` + ### Deploy free plugin example ```yaml From 6dfead11aa9aa27a2ceb74195a2bd9041287937e Mon Sep 17 00:00:00 2001 From: olatechpro Date: Mon, 18 May 2026 11:24:52 +0100 Subject: [PATCH 2/6] Fix "Directory to parse is missing or incorrect" error on workflow --- .github/workflows/code-complexity.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml index 673050c..a3f01ae 100644 --- a/.github/workflows/code-complexity.yml +++ b/.github/workflows/code-complexity.yml @@ -41,4 +41,4 @@ jobs: run: echo "$DEV_SCRIPTS_DIR" >> "$GITHUB_PATH" - name: Run code complexity analysis - run: composer metrics \ No newline at end of file + run: composer metrics -- . \ No newline at end of file From e248425bd86369cc553ec4a229e0b98f247b4ae2 Mon Sep 17 00:00:00 2001 From: olatechpro Date: Mon, 18 May 2026 11:37:42 +0100 Subject: [PATCH 3/6] The workflow should fail if error and critical is greater than 0 with actionable output --- .github/workflows/code-complexity.yml | 57 ++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml index a3f01ae..ce9ac0f 100644 --- a/.github/workflows/code-complexity.yml +++ b/.github/workflows/code-complexity.yml @@ -41,4 +41,59 @@ jobs: run: echo "$DEV_SCRIPTS_DIR" >> "$GITHUB_PATH" - name: Run code complexity analysis - run: composer metrics -- . \ No newline at end of file + run: | + set -o pipefail + composer metrics -- --report-violations=metrics/violations.xml . | tee metrics-output.log + + - name: Enforce complexity gate and show actionable violations + run: | + critical=$(grep -E "^[[:space:]]*Critical[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) + errors=$(grep -E "^[[:space:]]*Error[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) + + critical=${critical:-0} + errors=${errors:-0} + + echo "PHPMetrics summary: Critical=${critical}, Error=${errors}" + + if [ -f metrics/violations.xml ]; then + php -r ' + $file = "metrics/violations.xml"; + $doc = new DOMDocument(); + if (!$doc->load($file)) { + fwrite(STDERR, "Failed to parse violations file\n"); + exit(0); + } + + $files = $doc->getElementsByTagName("file"); + foreach ($files as $f) { + $path = $f->getAttribute("name"); + foreach ($f->getElementsByTagName("violation") as $v) { + $priority = (int) $v->getAttribute("priority"); + $line = (int) $v->getAttribute("beginline"); + if ($line <= 0) { + $line = (int) $v->getAttribute("line"); + } + + $message = trim(preg_replace("/\s+/", " ", $v->textContent)); + $severity = "notice"; + if ($priority > 0 && $priority <= 2) { + $severity = "error"; + } elseif ($priority === 3) { + $severity = "warning"; + } + + $label = $priority > 0 ? "P" . $priority : "P?"; + if ($line > 0) { + echo "::${severity} file=${path},line=${line}::[${label}] ${message}\n"; + } else { + echo "::${severity} file=${path}::[${label}] ${message}\n"; + } + } + } + ' + fi + + if [ "$critical" -gt 0 ] || [ "$errors" -gt 0 ]; then + echo "Failing workflow due to PHPMetrics violations (Critical=${critical}, Error=${errors})." + exit 1 + fi \ No newline at end of file From c51dfd2e4134dfcebf33b462ca6c11cfee770dfb Mon Sep 17 00:00:00 2001 From: olatechpro Date: Mon, 18 May 2026 11:50:37 +0100 Subject: [PATCH 4/6] Update the workflow to show only Critical/Error details and to include class/package context when file paths are unavailable. --- .github/workflows/code-complexity.yml | 43 ++++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml index ce9ac0f..7580d54 100644 --- a/.github/workflows/code-complexity.yml +++ b/.github/workflows/code-complexity.yml @@ -49,11 +49,19 @@ jobs: run: | critical=$(grep -E "^[[:space:]]*Critical[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) errors=$(grep -E "^[[:space:]]*Error[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) + warnings=$(grep -E "^[[:space:]]*Warning[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) + information=$(grep -E "^[[:space:]]*Information[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) critical=${critical:-0} errors=${errors:-0} + warnings=${warnings:-0} + information=${information:-0} - echo "PHPMetrics summary: Critical=${critical}, Error=${errors}" + echo "Violations" + printf " %-43s %s\n" "Critical" "${critical}" + printf " %-43s %s\n" "Error" "${errors}" + printf " %-43s %s\n" "Warning" "${warnings}" + printf " %-43s %s\n" "Information" "${information}" if [ -f metrics/violations.xml ]; then php -r ' @@ -66,27 +74,40 @@ jobs: $files = $doc->getElementsByTagName("file"); foreach ($files as $f) { - $path = $f->getAttribute("name"); foreach ($f->getElementsByTagName("violation") as $v) { $priority = (int) $v->getAttribute("priority"); + if ($priority > 2) { + continue; + } + + $path = $f->getAttribute("name"); + if ("" === $path) { + $path = $v->getAttribute("file"); + } + + $entity = $v->getAttribute("class"); + if ("" === $entity) { + $entity = $v->getAttribute("package"); + } + $line = (int) $v->getAttribute("beginline"); if ($line <= 0) { $line = (int) $v->getAttribute("line"); } $message = trim(preg_replace("/\s+/", " ", $v->textContent)); - $severity = "notice"; - if ($priority > 0 && $priority <= 2) { - $severity = "error"; - } elseif ($priority === 3) { - $severity = "warning"; + $label = $priority > 0 ? "P" . $priority : "P?"; + $entityLabel = ""; + if ("" !== $entity) { + $entityLabel = " [" . $entity . "]"; } - $label = $priority > 0 ? "P" . $priority : "P?"; - if ($line > 0) { - echo "::${severity} file=${path},line=${line}::[${label}] ${message}\n"; + if ("" !== $path && $line > 0) { + echo "::error file=${path},line=${line}::[${label}]${entityLabel} ${message}\n"; + } elseif ("" !== $path) { + echo "::error file=${path}::[${label}]${entityLabel} ${message}\n"; } else { - echo "::${severity} file=${path}::[${label}] ${message}\n"; + echo "::error::[${label}]${entityLabel} ${message}\n"; } } } From b2139a684e63133865752200f5febe956d6a4235 Mon Sep 17 00:00:00 2001 From: olatechpro Date: Mon, 18 May 2026 12:01:34 +0100 Subject: [PATCH 5/6] Update the emitted message text to include location context --- .github/workflows/code-complexity.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml index 7580d54..9d3b517 100644 --- a/.github/workflows/code-complexity.yml +++ b/.github/workflows/code-complexity.yml @@ -97,17 +97,23 @@ jobs: $message = trim(preg_replace("/\s+/", " ", $v->textContent)); $label = $priority > 0 ? "P" . $priority : "P?"; - $entityLabel = ""; - if ("" !== $entity) { - $entityLabel = " [" . $entity . "]"; + $context = ""; + if ("" !== $path && $line > 0) { + $context = $path . ":" . $line; + } elseif ("" !== $path) { + $context = $path; + } elseif ("" !== $entity) { + $context = $entity; } if ("" !== $path && $line > 0) { - echo "::error file=${path},line=${line}::[${label}]${entityLabel} ${message}\n"; + echo "::error file=${path},line=${line}::[${label}] [${context}] ${message}\n"; } elseif ("" !== $path) { - echo "::error file=${path}::[${label}]${entityLabel} ${message}\n"; + echo "::error file=${path}::[${label}] [${context}] ${message}\n"; + } elseif ("" !== $context) { + echo "::error::[${label}] [${context}] ${message}\n"; } else { - echo "::error::[${label}]${entityLabel} ${message}\n"; + echo "::error::[${label}] [no-source-reference] ${message}\n"; } } } From 15e29853dce4e982cdc3c15b8d5796464b55916a Mon Sep 17 00:00:00 2001 From: olatechpro Date: Mon, 18 May 2026 13:01:33 +0100 Subject: [PATCH 6/6] Switch to PHPMD instead of PHPMetrics due to missing code lines in the report --- .github/workflows/code-complexity.yml | 84 +-------------------------- README.md | 2 +- 2 files changed, 2 insertions(+), 84 deletions(-) diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml index 9d3b517..26256d4 100644 --- a/.github/workflows/code-complexity.yml +++ b/.github/workflows/code-complexity.yml @@ -41,86 +41,4 @@ jobs: run: echo "$DEV_SCRIPTS_DIR" >> "$GITHUB_PATH" - name: Run code complexity analysis - run: | - set -o pipefail - composer metrics -- --report-violations=metrics/violations.xml . | tee metrics-output.log - - - name: Enforce complexity gate and show actionable violations - run: | - critical=$(grep -E "^[[:space:]]*Critical[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) - errors=$(grep -E "^[[:space:]]*Error[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) - warnings=$(grep -E "^[[:space:]]*Warning[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) - information=$(grep -E "^[[:space:]]*Information[[:space:]]+[0-9]+" metrics-output.log | awk '{print $2}' | tail -n 1) - - critical=${critical:-0} - errors=${errors:-0} - warnings=${warnings:-0} - information=${information:-0} - - echo "Violations" - printf " %-43s %s\n" "Critical" "${critical}" - printf " %-43s %s\n" "Error" "${errors}" - printf " %-43s %s\n" "Warning" "${warnings}" - printf " %-43s %s\n" "Information" "${information}" - - if [ -f metrics/violations.xml ]; then - php -r ' - $file = "metrics/violations.xml"; - $doc = new DOMDocument(); - if (!$doc->load($file)) { - fwrite(STDERR, "Failed to parse violations file\n"); - exit(0); - } - - $files = $doc->getElementsByTagName("file"); - foreach ($files as $f) { - foreach ($f->getElementsByTagName("violation") as $v) { - $priority = (int) $v->getAttribute("priority"); - if ($priority > 2) { - continue; - } - - $path = $f->getAttribute("name"); - if ("" === $path) { - $path = $v->getAttribute("file"); - } - - $entity = $v->getAttribute("class"); - if ("" === $entity) { - $entity = $v->getAttribute("package"); - } - - $line = (int) $v->getAttribute("beginline"); - if ($line <= 0) { - $line = (int) $v->getAttribute("line"); - } - - $message = trim(preg_replace("/\s+/", " ", $v->textContent)); - $label = $priority > 0 ? "P" . $priority : "P?"; - $context = ""; - if ("" !== $path && $line > 0) { - $context = $path . ":" . $line; - } elseif ("" !== $path) { - $context = $path; - } elseif ("" !== $entity) { - $context = $entity; - } - - if ("" !== $path && $line > 0) { - echo "::error file=${path},line=${line}::[${label}] [${context}] ${message}\n"; - } elseif ("" !== $path) { - echo "::error file=${path}::[${label}] [${context}] ${message}\n"; - } elseif ("" !== $context) { - echo "::error::[${label}] [${context}] ${message}\n"; - } else { - echo "::error::[${label}] [no-source-reference] ${message}\n"; - } - } - } - ' - fi - - if [ "$critical" -gt 0 ] || [ "$errors" -gt 0 ]; then - echo "Failing workflow due to PHPMetrics violations (Critical=${critical}, Error=${errors})." - exit 1 - fi \ No newline at end of file + run: vendor/publishpress/dev-workspace/scripts/run.sh phpmd . text phpmd.ruleset.xml --exclude vendor,lib/vendor,tests,dist,.claude,.cursor,.github,.wordpress-org,.git,.zed,docs,languages \ No newline at end of file diff --git a/README.md b/README.md index 51bb247..b6949ae 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Reusable GitHub Actions workflows for PublishPress plugin repositories. - `.github/workflows/unit-tests.yml`: Runs PHPUnit tests. - `.github/workflows/code-standards.yml`: Runs PHP compatibility and lint checks. -- `.github/workflows/code-complexity.yml`: Runs PHP code complexity analysis with PHPMetrics. +- `.github/workflows/code-complexity.yml`: Runs PHP code complexity analysis with PHPMD. - `.github/workflows/deploy-free.yml`: Builds and deploys free plugin releases to WordPress.org and uploads release assets to GitHub. - `.github/workflows/deploy-free-assets.yml`: Updates WordPress.org plugin assets/readme. - `.github/workflows/deploy-pro.yml`: Builds pro plugin packages and uploads release assets to GitHub.