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
10 changes: 9 additions & 1 deletion .github/workflows/phpcs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ jobs:
echo "files=$FILES" >> $GITHUB_OUTPUT
echo "PHP files changed by this pull request: ${FILES:-none}" >> $GITHUB_STEP_SUMMARY

# Findings are filtered to the lines this pull request actually changed.
# PHPCS reports whole files, so without this, touching one line of a legacy
# file inherits every violation already in it and the job is red no matter
# what the author does. Measured on the Elementor branch: 175 security
# findings in the files it touches, but only 63 on lines it wrote.
- name: Detect coding standard violations
if: steps.changes.outputs.files != ''
run: phpcs --standard=phpcs.xml.dist ${{ steps.changes.outputs.files }} -q --report=checkstyle | cs2pr --graceful-warnings
run: |
set -o pipefail
phpcs --standard=phpcs.xml.dist ${{ steps.changes.outputs.files }} -q --report=json \
| php bin/phpcs-changed-lines.php "$(git rev-parse HEAD^1)"
114 changes: 114 additions & 0 deletions bin/phpcs-changed-lines.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
/**
* Filter a PHPCS JSON report down to the lines a pull request actually changed.
*
* PHPCS reports whole files. On a codebase with existing violations that means
* touching one line of a legacy file inherits every finding already in it, so
* the gate punishes people for editing old code and the job is red no matter
* what they do. This keeps only findings that sit on added or modified lines.
*
* Usage: phpcs --report=json <files> | php bin/phpcs-changed-lines.php <base-sha>
* Annotates every surviving finding, but only ERRORS fail the run. That mirrors
* cs2pr --graceful-warnings, which the Dokan and WPUF workflows already use.
*
* @package weDocs
*/

$base = $argv[1] ?? '';
if ( '' === $base ) {
fwrite( STDERR, "usage: phpcs --report=json ... | php {$argv[0]} <base-sha>\n" );
exit( 2 );
}

$raw = stream_get_contents( STDIN );
if ( '' === trim( $raw ) ) {
exit( 0 );
}

$report = json_decode( $raw, true );
if ( ! is_array( $report ) || empty( $report['files'] ) ) {
exit( 0 );
}

/**
* Line numbers added or modified in $file, relative to $base.
*/
function wedocs_changed_lines( $base, $file ) {
$cmd = sprintf( 'git diff --unified=0 %s -- %s', escapeshellarg( $base ), escapeshellarg( $file ) );
exec( $cmd, $out );

$lines = [];
$cursor = null;
foreach ( $out as $line ) {
if ( preg_match( '/^@@ -\S+ \+(\d+)(?:,(\d+))? @@/', $line, $m ) ) {
$cursor = (int) $m[1];
continue;
}
if ( null === $cursor ) {
continue;
}
if ( isset( $line[0] ) && '+' === $line[0] && 0 !== strpos( $line, '+++' ) ) {
$lines[ $cursor ] = true;
$cursor++;
} elseif ( isset( $line[0] ) && ( '-' === $line[0] || '\\' === $line[0] ) ) {
continue;
} else {
$cursor++;
}
}

return $lines;
}

$root = rtrim( shell_exec( 'git rev-parse --show-toplevel' ) ?: '', "\n" );
$kept = 0;
$errors = 0;
$total = 0;

foreach ( $report['files'] as $path => $data ) {
if ( empty( $data['messages'] ) ) {
continue;
}

$relative = $path;
if ( '' !== $root && 0 === strpos( $path, $root . '/' ) ) {
$relative = substr( $path, strlen( $root ) + 1 );
}

$changed = wedocs_changed_lines( $base, $relative );

foreach ( $data['messages'] as $message ) {
$total++;
if ( ! isset( $changed[ $message['line'] ] ) ) {
continue;
}

$kept++;
$is_error = ( 'ERROR' === $message['type'] );
if ( $is_error ) {
$errors++;
}

printf(
"::%s file=%s,line=%d,col=%d::%s (%s)\n",
$is_error ? 'error' : 'warning',
$relative,
$message['line'],
$message['column'],
str_replace( [ "\r", "\n" ], ' ', $message['message'] ),
$message['source']
);
}
}

fwrite(
STDERR,
sprintf(
"PHPCS: %d finding(s) on changed lines, %d of them errors (%d findings in these files overall).\n",
$kept,
$errors,
$total
)
);

exit( $errors > 0 ? 1 : 0 );
4 changes: 4 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<exclude-pattern>*/node_modules/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<!-- Developer tooling, not shipped plugin runtime: the WordPress sniffs for
global prefixes, WP_Filesystem and system calls do not apply to a CLI
script. WPUF excludes this directory for the same reason. -->
<exclude-pattern>*/bin/*</exclude-pattern>
<exclude-pattern>*.js</exclude-pattern>
<exclude-pattern>*.mo</exclude-pattern>
<exclude-pattern>*.po</exclude-pattern>
Expand Down
Loading