target = $target; } public static function unavailable_reason() { if ( ! is_readable( self::vendor_dir() . 'php_codesniffer/autoload.php' ) ) { return __( 'The bundled PHP_CodeSniffer files are missing. Reinstall the plugin.', 'leaplens' ); } if ( ! function_exists( 'token_get_all' ) ) { return __( 'The PHP tokenizer extension is disabled on this server.', 'leaplens' ); } foreach ( array( 'PHP_CodeSniffer\Autoload', 'PHP_CodeSniffer\Config' ) as $class ) { if ( class_exists( $class, false ) ) { $file = (string) ( new \ReflectionClass( $class ) )->getFileName(); if ( 0 !== strpos( wp_normalize_path( $file ), self::vendor_dir() ) ) { /* translators: %s: file path of the other copy. */ return sprintf( __( 'Another plugin already loaded its own copy of PHP_CodeSniffer (%s).', 'leaplens' ), $file ); } } } // On the command line PHP_CodeSniffer probes STDIN, which needs posix_isatty() or shell_exec(). if ( defined( 'STDIN' ) && ! function_exists( 'posix_isatty' ) && ! function_exists( 'shell_exec' ) ) { return __( 'Scanning from the command line needs the PHP posix extension. Continue the scan from the admin screen.', 'leaplens' ); } return ''; } public function booted() { return null !== $this->ruleset; } private static function vendor_dir() { return wp_normalize_path( LEAPLENS_DIR . 'vendor/' ); } private function boot() { if ( $this->ruleset ) { return; } $reason = self::unavailable_reason(); if ( '' !== $reason ) { throw new \RuntimeException( esc_html( $reason ) ); } $vendor = self::vendor_dir(); if ( ! defined( 'PHP_CODESNIFFER_VERBOSITY' ) ) { define( 'PHP_CODESNIFFER_VERBOSITY', 0 ); } if ( ! defined( 'PHP_CODESNIFFER_CBF' ) ) { define( 'PHP_CODESNIFFER_CBF', false ); } require_once $vendor . 'php_codesniffer/autoload.php'; require_once $vendor . 'phpcsutils/phpcsutils-autoload.php'; // Defines the T_* token constants the tokenizer relies on. class_exists( 'PHP_CodeSniffer\Util\Tokens' ); // Annotations are ignored: a "phpcs:ignore" in a plugin must not hide a removed function from this scan. $this->config = new Config( array( '--standard=' . $vendor . 'phpcompatibility/PHPCompatibility', '--runtime-set', 'testVersion', $this->target, '--ignore-annotations', '--no-cache', '-q' ), false ); $this->ruleset = new Ruleset( $this->config ); } /** Findings for one file as [line, type, code, message, guarded], sorted by line; one per line and rule, guarded only when every hit on the line is. Throws when the file cannot be analysed. */ public function check_file( $path ) { $this->boot(); $file = new LocalFile( $path, $this->ruleset, $this->config ); try { $file->process(); $out = array(); $guards = null; foreach ( array( 'error' => $file->getErrors(), 'warning' => $file->getWarnings() ) as $type => $lines ) { foreach ( $lines as $line => $columns ) { foreach ( $columns as $column => $messages ) { foreach ( $messages as $m ) { $code = (string) $m['source']; if ( 0 === strpos( $code, 'Internal.' ) ) { if ( in_array( $code, self::IGNORED_INTERNAL, true ) ) { continue; } throw new \RuntimeException( esc_html( wp_strip_all_tags( (string) $m['message'] ) ) ); } if ( null === $guards ) { $guards = new Guards( $file->getTokens(), $this->target ); } $key = $line . "\0" . $code; $guarded = $guards->skips( $line, $column, $type, $code ) && ( ! isset( $out[ $key ] ) || $out[ $key ][4] ); $out[ $key ] = array( (int) $line, $type, $code, (string) $m['message'], $guarded ); } } } } ksort( $out, SORT_NATURAL ); return array_values( $out ); } finally { $file->cleanUp(); // PHPCSUtils keeps per-file results in static caches meant for one CLI run; across hundreds of files they exhaust memory. \PHPCSUtils\Internal\Cache::clear(); \PHPCSUtils\Internal\NoFileCache::clear(); } } }