pax_global_header00006660000000000000000000000064133477606470014533gustar00rootroot0000000000000052 comment=0d9afa762f2400de077b2192f4a9d127de0bb78e phpcpd-4.1.0/000077500000000000000000000000001334776064700130135ustar00rootroot00000000000000phpcpd-4.1.0/.gitattributes000066400000000000000000000000171334776064700157040ustar00rootroot00000000000000*.php diff=php phpcpd-4.1.0/.github/000077500000000000000000000000001334776064700143535ustar00rootroot00000000000000phpcpd-4.1.0/.github/stale.yml000066400000000000000000000027011334776064700162060ustar00rootroot00000000000000# Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale Issue or Pull Request is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 7 # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - enhancement # Set to true to ignore issues in a project (defaults to false) exemptProjects: false # Set to true to ignore issues in a milestone (defaults to false) exemptMilestones: false # Label to use when marking as stale staleLabel: wontfix # Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had activity within the last 60 days. It will be closed after 7 days if no further activity occurs. Thank you for your contributions. # Comment to post when removing the stale label. # unmarkComment: > # Your comment here. # Comment to post when closing a stale Issue or Pull Request. closeComment: > This issue has been automatically closed because it has not had activity since it was marked as stale. Thank you for your contributions. # Limit the number of actions per hour, from 1-30. Default is 30 limitPerRun: 30 # Limit to only `issues` or `pulls` only: issues phpcpd-4.1.0/.gitignore000066400000000000000000000001161334776064700150010ustar00rootroot00000000000000/build/phar /build/*.phar* /.idea /composer.lock /vendor /tools /.php_cs.cachephpcpd-4.1.0/.php_cs.dist000066400000000000000000000171521334776064700152400ustar00rootroot00000000000000 For the full copyright and license information, please view the LICENSE file that was distributed with this source code. EOF; return PhpCsFixer\Config::create() ->setRiskyAllowed(true) ->setRules( [ 'align_multiline_comment' => true, 'array_indentation' => true, 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => [ 'operators' => [ '=' => 'align', '=>' => 'align', ], ], 'blank_line_after_namespace' => true, 'blank_line_before_statement' => [ 'statements' => [ 'break', 'continue', 'declare', 'do', 'for', 'foreach', 'if', 'include', 'include_once', 'require', 'require_once', 'return', 'switch', 'throw', 'try', 'while', 'yield', ], ], 'braces' => true, 'cast_spaces' => true, 'class_attributes_separation' => ['elements' => ['const', 'method', 'property']], 'combine_consecutive_issets' => true, 'combine_consecutive_unsets' => true, 'compact_nullable_typehint' => true, 'concat_space' => ['spacing' => 'one'], 'declare_equal_normalize' => ['space' => 'none'], 'dir_constant' => true, 'elseif' => true, 'encoding' => true, 'full_opening_tag' => true, 'function_declaration' => true, 'header_comment' => ['header' => $header, 'separate' => 'none'], 'indentation_type' => true, 'is_null' => true, 'line_ending' => true, 'list_syntax' => ['syntax' => 'short'], 'logical_operators' => true, 'lowercase_cast' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, 'lowercase_static_reference' => true, 'magic_constant_casing' => true, 'method_argument_space' => ['ensure_fully_multiline' => true], 'modernize_types_casting' => true, 'multiline_comment_opening_closing' => true, 'multiline_whitespace_before_semicolons' => true, 'native_constant_invocation' => true, 'native_function_casing' => true, 'native_function_invocation' => true, 'new_with_braces' => false, 'no_alias_functions' => true, 'no_alternative_syntax' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_blank_lines_before_namespace' => true, 'no_closing_tag' => true, 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_blank_lines' => true, 'no_homoglyph_names' => true, 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => ['use' => 'print'], 'no_multiline_whitespace_around_double_arrow' => true, 'no_null_property_initialization' => true, 'no_php4_constructor' => true, 'no_short_bool_cast' => true, 'no_short_echo_tag' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_after_function_name' => true, 'no_spaces_inside_parenthesis' => true, 'no_superfluous_elseif' => true, 'no_superfluous_phpdoc_tags' => true, 'no_trailing_comma_in_list_call' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'no_unneeded_control_parentheses' => true, 'no_unneeded_curly_braces' => true, 'no_unneeded_final_method' => true, 'no_unreachable_default_argument_value' => true, 'no_unset_on_property' => true, 'no_unused_imports' => true, 'no_useless_else' => true, 'no_useless_return' => true, 'no_whitespace_before_comma_in_array' => true, 'no_whitespace_in_blank_line' => true, 'non_printable_character' => true, 'normalize_index_brace' => true, 'object_operator_without_whitespace' => true, 'ordered_class_elements' => [ 'order' => [ 'use_trait', 'constant_public', 'constant_protected', 'constant_private', 'property_public_static', 'property_protected_static', 'property_private_static', 'property_public', 'property_protected', 'property_private', 'method_public_static', 'construct', 'destruct', 'magic', 'phpunit', 'method_public', 'method_protected', 'method_private', 'method_protected_static', 'method_private_static', ], ], 'ordered_imports' => true, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_align' => true, 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, 'phpdoc_no_access' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_no_package' => true, 'phpdoc_order' => true, 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, 'phpdoc_separation' => true, 'phpdoc_single_line_var_spacing' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_types' => true, 'phpdoc_types_order' => true, 'phpdoc_var_without_name' => true, 'pow_to_exponentiation' => true, 'protected_to_private' => true, 'return_assignment' => true, 'return_type_declaration' => ['space_before' => 'none'], 'self_accessor' => true, 'semicolon_after_instruction' => true, 'set_type_to_cast' => true, 'short_scalar_cast' => true, 'simplified_null_return' => true, 'single_blank_line_at_eof' => true, 'single_import_per_statement' => true, 'single_line_after_imports' => true, 'single_quote' => true, 'standardize_not_equals' => true, 'ternary_to_null_coalescing' => true, 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'visibility_required' => true, 'void_return' => true, 'whitespace_after_comma_in_array' => true, ] ) ->setFinder( PhpCsFixer\Finder::create() ->files() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ->notPath('_files') ); phpcpd-4.1.0/.travis.yml000066400000000000000000000012021334776064700151170ustar00rootroot00000000000000language: php sudo: false php: - 7.1 - 7.2 - master env: matrix: - DEPENDENCIES="high" - DEPENDENCIES="low" global: - DEFAULT_COMPOSER_FLAGS="--no-interaction --no-ansi --no-progress --no-suggest" before_install: - composer self-update - composer clear-cache - wget -O phpunit.phar https://phar.phpunit.de/phpunit-7.phar install: - if [[ "$DEPENDENCIES" = 'high' ]]; then travis_retry composer update $DEFAULT_COMPOSER_FLAGS; fi - if [[ "$DEPENDENCIES" = 'low' ]]; then travis_retry composer update $DEFAULT_COMPOSER_FLAGS --prefer-lowest; fi script: - php phpunit.phar notifications: email: false phpcpd-4.1.0/ChangeLog.md000066400000000000000000000031141334776064700151630ustar00rootroot00000000000000# Changes in PHPCPD All notable changes in PHPCPD are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. ## [4.1.0] - 2018-09-17 ### Added * Implemented [#117](https://github.com/sebastianbergmann/phpcpd/issues/117): Report average and maximum length of code clone ### Changed * The text logger now prints code clones sorted by size (in descending order) ## [4.0.0] - 2018-01-02 ### Removed * PHPCPD is no longer supported on PHP 5.6 and PHP 7.0 ## [3.0.1] - 2017-11-16 ### Fixed * Fixed [#147](https://github.com/sebastianbergmann/phpcpd/issues/147): Wrong exit code when no files were found to be scanned * Fixed [#152](https://github.com/sebastianbergmann/phpcpd/issues/152): Version requirement for `sebastian/version` is too strict ## [3.0.0] - 2017-02-05 ### Added * Merged [#90](https://github.com/sebastianbergmann/phpcpd/pull/90): The PMD logger now replaces all characters that are invalid XML with `U+FFFD` * Merged [#100](https://github.com/sebastianbergmann/phpcpd/pull/100): Added the `--regexps-exclude` option ### Changed * When the Xdebug extension is loaded, PHPCPD disables as much of Xdebug's functionality as possible to minimize the performance impact ### Removed * PHPCPD is no longer supported on PHP 5.3, PHP 5.4, and PHP 5.5 [4.1.0]: https://github.com/sebastianbergmann/phpcpd/compare/4.0.0...4.1.0 [4.0.0]: https://github.com/sebastianbergmann/phpcpd/compare/3.0.1...4.0.0 [3.0.1]: https://github.com/sebastianbergmann/phpcpd/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/sebastianbergmann/phpcpd/compare/2.0...3.0.0 phpcpd-4.1.0/LICENSE000066400000000000000000000030051334776064700140160ustar00rootroot00000000000000phpcpd Copyright (c) 2009-2018, Sebastian Bergmann . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sebastian Bergmann nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. phpcpd-4.1.0/README.md000066400000000000000000000035161334776064700142770ustar00rootroot00000000000000[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/phpcpd.svg?style=flat-square)](https://packagist.org/packages/sebastian/phpcpd) [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg?style=flat-square)](https://php.net/) [![Build Status](https://img.shields.io/travis/sebastianbergmann/phpcpd/master.svg?style=flat-square)](https://travis-ci.org/sebastianbergmann/phpcpd) # PHP Copy/Paste Detector (PHPCPD) `phpcpd` is a Copy/Paste Detector (CPD) for PHP code. ## Installation ### PHP Archive (PHAR) The easiest way to obtain PHPCPD is to download a [PHP Archive (PHAR)](http://php.net/phar) that has all required dependencies of PHPCPD bundled in a single file: ``` $ wget https://phar.phpunit.de/phpcpd.phar $ chmod +x phpcpd.phar $ mv phpcpd.phar /usr/local/bin/phpcpd ``` You can also immediately use the PHAR after you have downloaded it, of course: ``` $ wget https://phar.phpunit.de/phpcpd.phar $ php phpcpd.phar ``` ### Composer You can add this tool as a local, per-project, development-time dependency to your project using [Composer](https://getcomposer.org/): ``` $ composer require --dev sebastian/phpcpd ``` You can then invoke it using the `vendor/bin/phpcpd` executable. ## Usage Example ``` $ phpcpd --fuzzy wordpress-4.9.8 phpcpd 4.1.0 by Sebastian Bergmann. Found 66 clones with 3014 duplicated lines in 40 files: - /home/sb/wordpress-4.9.8/wp-includes/Requests/IRI.php:358-708 (350 lines) /home/sb/wordpress-4.9.8/wp-includes/SimplePie/IRI.php:404-754 . . . - /home/sb/wordpress-4.9.8/wp-includes/SimplePie/File.php:133-144 (11 lines) /home/sb/wordpress-4.9.8/wp-includes/SimplePie/File.php:215-226 0.86% duplicated lines out of 349460 total lines of code. Average size of duplication is 45 lines, largest clone has 350 of lines Time: 1.79 seconds, Memory: 272.00MB ``` phpcpd-4.1.0/build.xml000066400000000000000000000110611334776064700146330ustar00rootroot00000000000000 phpcpd-4.1.0/build/000077500000000000000000000000001334776064700141125ustar00rootroot00000000000000phpcpd-4.1.0/build/phar-autoload.php.in000066400000000000000000000024131334776064700177700ustar00rootroot00000000000000#!/usr/bin/env php ')) { fwrite( STDERR, sprintf( 'This version of PHPCPD is supported on PHP 5.6, PHP 7.0, and PHP 7.1.' . PHP_EOL . 'You are using PHP %s%s.' . PHP_EOL, PHP_VERSION, defined('PHP_BINARY') ? ' (' . PHP_BINARY . ')' : '' ) ); die(1); } if ($_SERVER['SCRIPT_NAME'] != '-') { $phar = realpath($_SERVER['SCRIPT_NAME']); } else { $files = get_included_files(); $phar = $files[0]; } define('__PHPCPD_PHAR__', str_replace(DIRECTORY_SEPARATOR, '/', $phar)); define('__PHPCPD_PHAR_ROOT__', 'phar://___PHAR___'); spl_autoload_register( function ($class) { static $classes = NULL; if ($classes === NULL) { $classes = array( ___CLASSLIST___ ); } $class = strtolower($class); if (isset($classes[$class])) { require 'phar://___PHAR___' . $classes[$class]; } } ); Phar::mapPhar('___PHAR___'); if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') { print file_get_contents(__PHPCPD_PHAR_ROOT__ . '/manifest.txt'); exit; } $application = new SebastianBergmann\PHPCPD\CLI\Application; $application->run(); __HALT_COMPILER(); phpcpd-4.1.0/build/phar-manifest.php000077500000000000000000000013421334776064700173640ustar00rootroot00000000000000#!/usr/bin/env php &1'); if (strpos($tag, '-') === false && strpos($tag, 'No names found') === false) { print $tag; } else { $branch = @exec('git rev-parse --abbrev-ref HEAD'); $hash = @exec('git log -1 --format="%H"'); print $branch . '@' . $hash; } print "\n"; $lock = json_decode(file_get_contents(__DIR__ . '/../composer.lock')); foreach ($lock->packages as $package) { print $package->name . ': ' . $package->version; if (!preg_match('/^[v= ]*(([0-9]+)(\\.([0-9]+)(\\.([0-9]+)(-([0-9]+))?(-?([a-zA-Z-+][a-zA-Z0-9\\.\\-:]*)?)?)?)?)$/', $package->version)) { print '@' . $package->source->reference; } print "\n"; } phpcpd-4.1.0/composer.json000066400000000000000000000020511334776064700155330ustar00rootroot00000000000000{ "name": "sebastian/phpcpd", "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", "license": "BSD-3-Clause", "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "support": { "issues": "https://github.com/sebastianbergmann/phpcpd/issues" }, "config": { "platform": { "php": "7.1.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { "php": "^7.1", "ext-dom": "*", "sebastian/finder-facade": "^1.1", "sebastian/version": "^1.0|^2.0", "symfony/console": "^2.7|^3.0|^4.0", "phpunit/php-timer": "^2.0" }, "autoload": { "classmap": [ "src/" ] }, "bin": [ "phpcpd" ], "extra": { "branch-alias": { "dev-master": "4.0-dev" } } } phpcpd-4.1.0/phive.xml000066400000000000000000000003671334776064700146560ustar00rootroot00000000000000 phpcpd-4.1.0/phpcpd000077500000000000000000000025171334776064700142240ustar00rootroot00000000000000#!/usr/bin/env php * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (version_compare('7.1.0', PHP_VERSION, '>')) { fwrite( STDERR, sprintf( 'This version of PHPCPD is supported on PHP 7.1 (and newer).' . PHP_EOL . 'You are using PHP %s%s.' . PHP_EOL, PHP_VERSION, defined('PHP_BINARY') ? ' (' . PHP_BINARY . ')' : '' ) ); die(1); } // @see https://github.com/sebastianbergmann/phpcpd/issues/18 ini_set('mbstring.func_overload', 0); if (ini_get('mbstring.internal_encoding')) { ini_set('mbstring.internal_encoding', NULL); } $loaded = false; foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/vendor/autoload.php') as $file) { if (file_exists($file)) { require $file; $loaded = true; break; } } if (!$loaded) { die( 'You need to set up the project dependencies using the following commands:' . PHP_EOL . 'wget http://getcomposer.org/composer.phar' . PHP_EOL . 'php composer.phar install' . PHP_EOL ); } $application = new SebastianBergmann\PHPCPD\CLI\Application; $application->run(); phpcpd-4.1.0/phpunit.xml000066400000000000000000000013631334776064700152270ustar00rootroot00000000000000 tests src phpcpd-4.1.0/src/000077500000000000000000000000001334776064700136025ustar00rootroot00000000000000phpcpd-4.1.0/src/CLI/000077500000000000000000000000001334776064700142115ustar00rootroot00000000000000phpcpd-4.1.0/src/CLI/Application.php000066400000000000000000000050451334776064700171710ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\CLI; use SebastianBergmann\Version; use Symfony\Component\Console\Application as AbstractApplication; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; final class Application extends AbstractApplication { public function __construct() { $version = new Version('4.1.0', \dirname(__DIR__, 2)); parent::__construct('phpcpd', $version->getVersion()); } /** * Overridden so that the application doesn't expect the command * name to be the first argument. */ public function getDefinition() { $inputDefinition = parent::getDefinition(); $inputDefinition->setArguments(); return $inputDefinition; } /** * Runs the current application. */ public function doRun(InputInterface $input, OutputInterface $output): int { $this->disableXdebug(); if (!$input->hasParameterOption('--quiet')) { $output->write( \sprintf( "phpcpd %s by Sebastian Bergmann.\n\n", $this->getVersion() ) ); } if ($input->hasParameterOption('--version') || $input->hasParameterOption('-V')) { exit; } if (!$input->getFirstArgument()) { $input = new ArrayInput(['--help']); } return (int) parent::doRun($input, $output); } /** * Gets the name of the command based on input. */ protected function getCommandName(InputInterface $input): string { return 'phpcpd'; } /** * Gets the default commands that should always be available. */ protected function getDefaultCommands(): array { $defaultCommands = parent::getDefaultCommands(); $defaultCommands[] = new Command; return $defaultCommands; } private function disableXdebug(): void { if (!\extension_loaded('xdebug')) { return; } \ini_set('xdebug.scream', 0); \ini_set('xdebug.max_nesting_level', 8192); \ini_set('xdebug.show_exception_trace', 0); \ini_set('xdebug.show_error_trace', 0); \xdebug_disable(); } } phpcpd-4.1.0/src/CLI/Command.php000066400000000000000000000124651334776064700163100ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\CLI; use SebastianBergmann\FinderFacade\FinderFacade; use SebastianBergmann\PHPCPD\Detector\Detector; use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy; use SebastianBergmann\PHPCPD\Log\PMD; use SebastianBergmann\PHPCPD\Log\Text; use SebastianBergmann\Timer\Timer; use Symfony\Component\Console\Command\Command as AbstractCommand; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; final class Command extends AbstractCommand { /** * Configures the current command. */ protected function configure(): void { $this->setName('phpcpd') ->setDefinition( [ new InputArgument( 'values', InputArgument::IS_ARRAY, 'Files and directories to analyze' ), ] ) ->addOption( 'names', null, InputOption::VALUE_REQUIRED, 'A comma-separated list of file names to check', ['*.php'] ) ->addOption( 'names-exclude', null, InputOption::VALUE_REQUIRED, 'A comma-separated list of file names to exclude', [] ) ->addOption( 'regexps-exclude', null, InputOption::VALUE_REQUIRED, 'A comma-separated list of paths regexps to exclude (example: "#var/.*_tmp#")', [] ) ->addOption( 'exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Exclude a directory from code analysis (must be relative to source)' ) ->addOption( 'log-pmd', null, InputOption::VALUE_REQUIRED, 'Write result in PMD-CPD XML format to file' ) ->addOption( 'min-lines', null, InputOption::VALUE_REQUIRED, 'Minimum number of identical lines', 5 ) ->addOption( 'min-tokens', null, InputOption::VALUE_REQUIRED, 'Minimum number of identical tokens', 70 ) ->addOption( 'fuzzy', null, InputOption::VALUE_NONE, 'Fuzz variable names' ) ->addOption( 'progress', null, InputOption::VALUE_NONE, 'Show progress bar' ); } /** * Executes the current command. */ protected function execute(InputInterface $input, OutputInterface $output) { $finder = new FinderFacade( $input->getArgument('values'), $input->getOption('exclude'), $this->handleCSVOption($input, 'names'), $this->handleCSVOption($input, 'names-exclude'), $this->handleCSVOption($input, 'regexps-exclude') ); $files = $finder->findFiles(); if (empty($files)) { $output->writeln('No files found to scan'); exit(0); } $progressBar = null; if ($input->getOption('progress')) { $progressBar = new ProgressBar($output, \count($files)); $progressBar->start(); } $strategy = new DefaultStrategy; $detector = new Detector($strategy, $progressBar); $quiet = $output->getVerbosity() == OutputInterface::VERBOSITY_QUIET; $clones = $detector->copyPasteDetection( $files, $input->getOption('min-lines'), $input->getOption('min-tokens'), $input->getOption('fuzzy') ); if ($input->getOption('progress')) { $progressBar->finish(); $output->writeln("\n"); } if (!$quiet) { $printer = new Text; $printer->printResult($output, $clones); unset($printer); } $logPmd = $input->getOption('log-pmd'); if ($logPmd) { $pmd = new PMD($logPmd); $pmd->processClones($clones); unset($pmd); } if (!$quiet) { print Timer::resourceUsage() . "\n"; } if (\count($clones) > 0) { exit(1); } } private function handleCSVOption(InputInterface $input, string $option): array { $result = $input->getOption($option); if (!\is_array($result)) { $result = \explode(',', $result); \array_map('trim', $result); } return $result; } } phpcpd-4.1.0/src/CodeClone.php000066400000000000000000000042251334776064700161510ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; final class CodeClone { /** * @var int Size of the clone (lines) */ private $size; /** * @var int Size of the clone (tokens) */ private $tokens; /** * @var CodeCloneFile[] Files with this code clone */ private $files = []; /** * @var string Unique ID of Code Duplicate Fragment */ private $id; /** * @var string Lines of the clone */ private $lines = ''; public function __construct(CodeCloneFile $fileA, CodeCloneFile $fileB, int $size, int $tokens) { $this->addFile($fileA); $this->addFile($fileB); $this->size = $size; $this->tokens = $tokens; $this->id = \md5($this->getLines()); } public function addFile(CodeCloneFile $file): void { $id = $file->getId(); if (!isset($this->files[$id])) { $this->files[$id] = $file; } } /** * @return CodeCloneFile[] */ public function getFiles(): array { return $this->files; } public function getLines($indent = ''): string { if (empty($this->lines)) { $file = \current($this->files); $this->lines = \implode( '', \array_map( function ($line) use ($indent) { return $indent . $line; }, \array_slice( \file($file->getName()), $file->getStartLine() - 1, $this->size ) ) ); } return $this->lines; } public function getId(): string { return $this->id; } public function getSize(): int { return $this->size; } public function getTokens(): int { return $this->tokens; } } phpcpd-4.1.0/src/CodeCloneFile.php000066400000000000000000000016511334776064700167510ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; final class CodeCloneFile { /** * @var string */ private $id; /** * @var string */ private $name; /** * @var int */ private $startLine; public function __construct(string $name, int $startLine) { $this->name = $name; $this->startLine = $startLine; $this->id = $this->name . ':' . $this->startLine; } public function getId(): string { return $this->id; } public function getName(): string { return $this->name; } public function getStartLine(): int { return $this->startLine; } } phpcpd-4.1.0/src/CodeCloneMap.php000066400000000000000000000055301334776064700166070ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; final class CodeCloneMap implements \Countable, \IteratorAggregate { /** * @var CodeClone[] */ private $clones = []; /** * @var CodeClone[] */ private $clonesById = []; /** * @var int */ private $numberOfDuplicatedLines = 0; /** * @var int */ private $numLines = 0; /** * @var int */ private $largestCloneSize = 0; /** * @var array */ private $filesWithClones = []; public function addClone(CodeClone $clone): void { $id = $clone->getId(); if (!isset($this->clonesById[$id])) { $this->clones[] = $clone; $this->clonesById[$id] = $clone; } else { $existClone = $this->clonesById[$id]; foreach ($clone->getFiles() as $file) { $existClone->addFile($file); } } $this->numberOfDuplicatedLines += $clone->getSize() * (\count($clone->getFiles()) - 1); foreach ($clone->getFiles() as $file) { if (!isset($this->filesWithClones[$file->getName()])) { $this->filesWithClones[$file->getName()] = true; } } $this->largestCloneSize = \max($this->largestCloneSize, $clone->getSize()); } /** * @return CodeClone[] */ public function getClones(): array { return $this->clones; } public function getPercentage(): string { if ($this->numLines > 0) { $percent = ($this->numberOfDuplicatedLines / $this->numLines) * 100; } else { $percent = 100; } return \sprintf('%01.2F%%', $percent); } public function getNumLines(): int { return $this->numLines; } public function setNumLines(int $numLines): void { $this->numLines = $numLines; } public function count(): int { return \count($this->clones); } public function getNumberOfFilesWithClones(): int { return \count($this->filesWithClones); } public function getNumberOfDuplicatedLines(): int { return $this->numberOfDuplicatedLines; } public function getIterator(): CodeCloneMapIterator { return new CodeCloneMapIterator($this); } public function isEmpty(): bool { return empty($this->clones); } public function getAverageSize(): int { return $this->getNumberOfDuplicatedLines() / $this->count(); } public function getLargestSize(): int { return $this->largestCloneSize; } } phpcpd-4.1.0/src/CodeCloneMapIterator.php000066400000000000000000000023471334776064700203240ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; final class CodeCloneMapIterator implements \Iterator { /** * @var CodeClone[] */ private $clones = []; /** * @var int */ private $position = 0; public function __construct(CodeCloneMap $clones) { $this->clones = $clones->getClones(); \usort( $this->clones, function (CodeClone $a, CodeClone $b): int { return $a->getSize() <=> $b->getSize(); } ); $this->clones = \array_reverse($this->clones); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < \count($this->clones); } public function key(): int { return $this->position; } public function current(): CodeClone { return $this->clones[$this->position]; } public function next(): void { $this->position++; } } phpcpd-4.1.0/src/Detector/000077500000000000000000000000001334776064700153535ustar00rootroot00000000000000phpcpd-4.1.0/src/Detector/Detector.php000066400000000000000000000027361334776064700176450ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Detector; use SebastianBergmann\PHPCPD\CodeCloneMap; use SebastianBergmann\PHPCPD\Detector\Strategy\AbstractStrategy; use Symfony\Component\Console\Helper\ProgressBar; final class Detector { /** * @var \SebastianBergmann\PHPCPD\Detector\Strategy\AbstractStrategy */ private $strategy; /** * @var \Symfony\Component\Console\Helper\ProgressBar */ private $progressBar; public function __construct(AbstractStrategy $strategy, ProgressBar $progressBar = null) { $this->strategy = $strategy; $this->progressBar = $progressBar; } public function copyPasteDetection(iterable $files, int $minLines = 5, int $minTokens = 70, bool $fuzzy = false): CodeCloneMap { $result = new CodeCloneMap; foreach ($files as $file) { if (empty($file)) { continue; } $this->strategy->processFile( $file, $minLines, $minTokens, $result, $fuzzy ); if ($this->progressBar !== null) { $this->progressBar->advance(); } } return $result; } } phpcpd-4.1.0/src/Detector/Strategy/000077500000000000000000000000001334776064700171555ustar00rootroot00000000000000phpcpd-4.1.0/src/Detector/Strategy/AbstractStrategy.php000066400000000000000000000017731334776064700231640ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Detector\Strategy; use SebastianBergmann\PHPCPD\CodeCloneMap; abstract class AbstractStrategy { /** * @var int[] */ protected $tokensIgnoreList = [ \T_INLINE_HTML => true, \T_COMMENT => true, \T_DOC_COMMENT => true, \T_OPEN_TAG => true, \T_OPEN_TAG_WITH_ECHO => true, \T_CLOSE_TAG => true, \T_WHITESPACE => true, \T_USE => true, \T_NS_SEPARATOR => true, ]; /** * @var string[] */ protected $hashes = []; abstract public function processFile(string $file, int $minLines, int $minTokens, CodeCloneMap $result, bool $fuzzy = false): void; } phpcpd-4.1.0/src/Detector/Strategy/DefaultStrategy.php000066400000000000000000000122641334776064700230020ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Detector\Strategy; use SebastianBergmann\PHPCPD\CodeClone; use SebastianBergmann\PHPCPD\CodeCloneFile; use SebastianBergmann\PHPCPD\CodeCloneMap; class DefaultStrategy extends AbstractStrategy { public function processFile(string $file, int $minLines, int $minTokens, CodeCloneMap $result, bool $fuzzy = false): void { $buffer = \file_get_contents($file); $currentTokenPositions = []; $currentTokenRealPositions = []; $currentSignature = ''; $tokens = \token_get_all($buffer); $tokenNr = 0; $lastTokenLine = 0; $result->setNumLines( $result->getNumLines() + \substr_count($buffer, "\n") ); unset($buffer); foreach (\array_keys($tokens) as $key) { $token = $tokens[$key]; if (\is_array($token)) { if (!isset($this->tokensIgnoreList[$token[0]])) { if ($tokenNr === 0) { $currentTokenPositions[$tokenNr] = $token[2] - $lastTokenLine; } else { $currentTokenPositions[$tokenNr] = $currentTokenPositions[$tokenNr - 1] + $token[2] - $lastTokenLine; } $currentTokenRealPositions[$tokenNr++] = $token[2]; if ($fuzzy && $token[0] === \T_VARIABLE) { $token[1] = 'variable'; } $currentSignature .= \chr($token[0] & 255) . \pack('N*', \crc32($token[1])); } $lastTokenLine = $token[2]; } } $count = \count($currentTokenPositions); $firstLine = 0; $firstRealLine = 0; $found = false; $tokenNr = 0; while ($tokenNr <= $count - $minTokens) { $line = $currentTokenPositions[$tokenNr]; $realLine = $currentTokenRealPositions[$tokenNr]; $hash = \substr( \md5( \substr( $currentSignature, $tokenNr * 5, $minTokens * 5 ), true ), 0, 8 ); if (isset($this->hashes[$hash])) { $found = true; if ($firstLine === 0) { $firstLine = $line; $firstRealLine = $realLine; $firstHash = $hash; $firstToken = $tokenNr; } } else { if ($found) { $fileA = $this->hashes[$firstHash][0]; $firstLineA = $this->hashes[$firstHash][1]; $lastToken = ($tokenNr - 1) + $minTokens - 1; $lastLine = $currentTokenPositions[$lastToken]; $lastRealLine = $currentTokenRealPositions[$lastToken]; $numLines = $lastLine + 1 - $firstLine; $realNumLines = $lastRealLine + 1 - $firstRealLine; if ($numLines >= $minLines && ($fileA !== $file || $firstLineA !== $firstRealLine)) { $result->addClone( new CodeClone( new CodeCloneFile($fileA, $firstLineA), new CodeCloneFile($file, $firstRealLine), $realNumLines, $lastToken + 1 - $firstToken ) ); } $found = false; $firstLine = 0; } $this->hashes[$hash] = [$file, $realLine]; } $tokenNr++; } if ($found) { $fileA = $this->hashes[$firstHash][0]; $firstLineA = $this->hashes[$firstHash][1]; $lastToken = ($tokenNr - 1) + $minTokens - 1; $lastLine = $currentTokenPositions[$lastToken]; $lastRealLine = $currentTokenRealPositions[$lastToken]; $numLines = $lastLine + 1 - $firstLine; $realNumLines = $lastRealLine + 1 - $firstRealLine; if ($numLines >= $minLines && ($fileA !== $file || $firstLineA !== $firstRealLine)) { $result->addClone( new CodeClone( new CodeCloneFile($fileA, $firstLineA), new CodeCloneFile($file, $firstRealLine), $realNumLines, $lastToken + 1 - $firstToken ) ); } } } } phpcpd-4.1.0/src/Log/000077500000000000000000000000001334776064700143235ustar00rootroot00000000000000phpcpd-4.1.0/src/Log/AbstractXmlLogger.php000066400000000000000000000055271334776064700204310ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use SebastianBergmann\PHPCPD\CodeCloneMap; abstract class AbstractXmlLogger { /** * @var \DOMDocument */ protected $document; /** * Constructor. */ public function __construct(string $filename) { $this->document = new \DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->filename = $filename; } /** * Processes a list of clones. */ abstract public function processClones(CodeCloneMap $clones); /** * Writes the XML document to the file. */ protected function flush(): void { \file_put_contents($this->filename, $this->document->saveXML()); } /** * Converts a string to UTF-8 encoding. */ protected function convertToUtf8(string $string): string { if (!$this->isUtf8($string)) { if (\function_exists('mb_convert_encoding')) { $string = \mb_convert_encoding($string, 'UTF-8'); } else { $string = \utf8_encode($string); } } return $string; } /** * Checks a string for UTF-8 encoding. */ protected function isUtf8(string $string): string { $length = \strlen($string); for ($i = 0; $i < $length; $i++) { if (\ord($string[$i]) < 0x80) { $n = 0; } elseif ((\ord($string[$i]) & 0xE0) == 0xC0) { $n = 1; } elseif ((\ord($string[$i]) & 0xF0) == 0xE0) { $n = 2; } elseif ((\ord($string[$i]) & 0xF0) == 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i == $length) || ((\ord($string[$i]) & 0xC0) != 0x80)) { return false; } } } return true; } /** * Escapes a string for inclusion inside an XML tag. * * Converts the string to UTF-8, substitutes the unicode replacement * character for every character disallowed in XML, and escapes * special characters. */ protected function escapeForXml(string $string): string { $string = $this->convertToUtf8($string); // Substitute the unicode replacement character for disallowed chars $string = \preg_replace( '/[^\x09\x0A\x0D\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]/u', "\xEF\xBF\xBD", $string ); return \htmlspecialchars($string, \ENT_COMPAT, 'UTF-8'); } } phpcpd-4.1.0/src/Log/PMD.php000066400000000000000000000027601334776064700154610ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use SebastianBergmann\PHPCPD\CodeCloneMap; final class PMD extends AbstractXmlLogger { /** * Processes a list of clones. */ public function processClones(CodeCloneMap $clones): void { $cpd = $this->document->createElement('pmd-cpd'); $this->document->appendChild($cpd); foreach ($clones as $clone) { $duplication = $cpd->appendChild( $this->document->createElement('duplication') ); $duplication->setAttribute('lines', $clone->getSize()); $duplication->setAttribute('tokens', $clone->getTokens()); foreach ($clone->getFiles() as $codeCloneFile) { $file = $duplication->appendChild( $this->document->createElement('file') ); $file->setAttribute('path', $codeCloneFile->getName()); $file->setAttribute('line', $codeCloneFile->getStartLine()); } $duplication->appendChild( $this->document->createElement( 'codefragment', $this->escapeForXml($clone->getLines()) ) ); } $this->flush(); } } phpcpd-4.1.0/src/Log/Text.php000066400000000000000000000044771334776064700157740ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use SebastianBergmann\PHPCPD\CodeCloneMap; use Symfony\Component\Console\Output\OutputInterface; final class Text { /** * Prints a result set from Detector::copyPasteDetection(). */ public function printResult(OutputInterface $output, CodeCloneMap $clones): void { $verbose = $output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL; if (\count($clones) > 0) { $output->write( \sprintf( 'Found %d clones with %d duplicated lines in %d files:' . \PHP_EOL . \PHP_EOL, \count($clones), $clones->getNumberOfDuplicatedLines(), $clones->getNumberOfFilesWithClones() ) ); } foreach ($clones as $clone) { $firstOccurrence = true; foreach ($clone->getFiles() as $file) { $output->writeln( \sprintf( ' %s%s:%d-%d%s', $firstOccurrence ? '- ' : ' ', $file->getName(), $file->getStartLine(), $file->getStartLine() + $clone->getSize(), $firstOccurrence ? ' (' . $clone->getSize() . ' lines)' : '' ) ); $firstOccurrence = false; } if ($verbose) { $output->write(\PHP_EOL . $clone->getLines(' ')); } $output->writeln(''); } if ($clones->isEmpty()) { $output->write("No clones found.\n\n"); return; } $output->write( \sprintf( "%s duplicated lines out of %d total lines of code.\n" . "Average size of duplication is %d lines, largest clone has %d of lines\n\n", $clones->getPercentage(), $clones->getNumLines(), $clones->getAverageSize(), $clones->getLargestSize() ) ); } } phpcpd-4.1.0/tests/000077500000000000000000000000001334776064700141555ustar00rootroot00000000000000phpcpd-4.1.0/tests/DetectorTest.php000066400000000000000000000161241334776064700173030ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Detector; if (!\defined('TEST_FILES_PATH')) { \define( 'TEST_FILES_PATH', __DIR__ . \DIRECTORY_SEPARATOR . '_files' . \DIRECTORY_SEPARATOR ); } use PHPUnit\Framework\TestCase; use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy; /** * @covers \SebastianBergmann\PHPCPD\Detector\Detector * @covers \SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy * * @uses \SebastianBergmann\PHPCPD\CodeClone * @uses \SebastianBergmann\PHPCPD\CodeCloneFile * @uses \SebastianBergmann\PHPCPD\CodeCloneMap */ class DetectorTest extends TestCase { /** * @dataProvider strategyProvider */ public function testDetectingSimpleClonesWorks($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [TEST_FILES_PATH . 'Math.php'] ); $clones = $clones->getClones(); $files = $clones[0]->getFiles(); $file = \current($files); $this->assertEquals(TEST_FILES_PATH . 'Math.php', $file->getName()); $this->assertEquals(75, $file->getStartLine()); $file = \next($files); $this->assertEquals(TEST_FILES_PATH . 'Math.php', $file->getName()); $this->assertEquals(139, $file->getStartLine()); $this->assertEquals(59, $clones[0]->getSize()); $this->assertEquals(136, $clones[0]->getTokens()); $this->assertEquals( ' public function div($v1, $v2) { $v3 = $v1 / ($v2 + $v1); if ($v3 > 14) { $v4 = 0; for ($i = 0; $i < $v3; $i++) { $v4 += ($v2 * $i); } } $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3)); $v6 = ($v1 * $v2 * $v3 * $v4 * $v5); $d = array($v1, $v2, $v3, $v4, $v5, $v6); $v7 = 1; for ($i = 0; $i < $v6; $i++) { shuffle( $d ); $v7 = $v7 + $i * end($d); } $v8 = $v7; foreach ( $d as $x ) { $v8 *= $x; } $v3 = $v1 / ($v2 + $v1); if ($v3 > 14) { $v4 = 0; for ($i = 0; $i < $v3; $i++) { $v4 += ($v2 * $i); } } $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3)); $v6 = ($v1 * $v2 * $v3 * $v4 * $v5); $d = array($v1, $v2, $v3, $v4, $v5, $v6); $v7 = 1; for ($i = 0; $i < $v6; $i++) { shuffle( $d ); $v7 = $v7 + $i * end($d); } $v8 = $v7; foreach ( $d as $x ) { $v8 *= $x; } return $v8; ', $clones[0]->getLines() ); } /** * @dataProvider strategyProvider */ public function testDetectingExactDuplicateFilesWorks($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection([ TEST_FILES_PATH . 'a.php', TEST_FILES_PATH . 'b.php', ], 20, 60); $clones = $clones->getClones(); $files = $clones[0]->getFiles(); $file = \current($files); $this->assertCount(1, $clones); $this->assertEquals(TEST_FILES_PATH . 'a.php', $file->getName()); $this->assertEquals(4, $file->getStartLine()); $file = \next($files); $this->assertEquals(TEST_FILES_PATH . 'b.php', $file->getName()); $this->assertEquals(4, $file->getStartLine()); $this->assertEquals(20, $clones[0]->getSize()); $this->assertEquals(60, $clones[0]->getTokens()); } /** * @dataProvider strategyProvider */ public function testDetectingClonesInMoreThanTwoFiles($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [ TEST_FILES_PATH . 'a.php', TEST_FILES_PATH . 'b.php', TEST_FILES_PATH . 'c.php', ], 20, 60 ); $clones = $clones->getClones(); $files = $clones[0]->getFiles(); \sort($files); $file = \current($files); $this->assertCount(1, $clones); $this->assertEquals(TEST_FILES_PATH . 'a.php', $file->getName()); $this->assertEquals(4, $file->getStartLine()); $file = \next($files); $this->assertEquals(TEST_FILES_PATH . 'b.php', $file->getName()); $this->assertEquals(4, $file->getStartLine()); $file = \next($files); $this->assertEquals(TEST_FILES_PATH . 'c.php', $file->getName()); $this->assertEquals(4, $file->getStartLine()); } /** * @dataProvider strategyProvider */ public function testClonesAreIgnoredIfTheySpanLessTokensThanMinTokens($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [ TEST_FILES_PATH . 'a.php', TEST_FILES_PATH . 'b.php', ], 20, 61 ); $this->assertCount(0, $clones->getClones()); } /** * @dataProvider strategyProvider */ public function testClonesAreIgnoredIfTheySpanLessLinesThanMinLines($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [ TEST_FILES_PATH . 'a.php', TEST_FILES_PATH . 'b.php', ], 21, 60 ); $this->assertCount(0, $clones->getClones()); } /** * @dataProvider strategyProvider */ public function testFuzzyClonesAreFound($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [ TEST_FILES_PATH . 'a.php', TEST_FILES_PATH . 'd.php', ], 5, 20, true ); $clones = $clones->getClones(); $this->assertCount(1, $clones); } /** * @dataProvider strategyProvider */ public function testStripComments($strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [ TEST_FILES_PATH . 'e.php', TEST_FILES_PATH . 'f.php', ], 8, 10, true ); $clones = $clones->getClones(); $this->assertCount(0, $clones); $clones = $detector->copyPasteDetection( [ TEST_FILES_PATH . 'e.php', TEST_FILES_PATH . 'f.php', ], 7, 10, true ); $clones = $clones->getClones(); $this->assertCount(1, $clones); } public function strategyProvider() { return [ [DefaultStrategy::class], ]; } } phpcpd-4.1.0/tests/Log/000077500000000000000000000000001334776064700146765ustar00rootroot00000000000000phpcpd-4.1.0/tests/Log/PMDTest.php000066400000000000000000000051101334776064700166640ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use PHPUnit\Framework\TestCase; use SebastianBergmann\PHPCPD\CodeClone; use SebastianBergmann\PHPCPD\CodeCloneFile; use SebastianBergmann\PHPCPD\CodeCloneMap; /** * @covers \SebastianBergmann\PHPCPD\Log\PMD * @covers \SebastianBergmann\PHPCPD\Log\AbstractXmlLogger * * @uses \SebastianBergmann\PHPCPD\CodeClone * @uses \SebastianBergmann\PHPCPD\CodeCloneFile * @uses \SebastianBergmann\PHPCPD\CodeCloneMap * @uses \SebastianBergmann\PHPCPD\CodeCloneMapIterator */ class PMDTest extends TestCase { /** @var string */ private $testFile1; /** @var @var string */ private $testFile2; /** @var string */ private $pmdLogFile; /** @var string */ private $expectedPmdLogFile; /** @var \SebastianBergmann\PHPCPD\Log\PMD */ private $pmdLogger; protected function setUp(): void { $this->testFile1 = __DIR__ . '/_files/with_ascii_escape.php'; $this->testFile2 = __DIR__ . '/_files/with_ascii_escape2.php'; $this->pmdLogFile = \tempnam(\sys_get_temp_dir(), 'pmd'); $this->expectedPmdLogFile = \tempnam(\sys_get_temp_dir(), 'pmd'); $expectedPmdLogTemplate = __DIR__ . '/_files/pmd_expected.xml'; $expectedPmdLogContents = \strtr( \file_get_contents($expectedPmdLogTemplate), [ '%file1%' => $this->testFile1, '%file2%' => $this->testFile2, ] ); \file_put_contents($this->expectedPmdLogFile, $expectedPmdLogContents); $this->pmdLogger = new PMD($this->pmdLogFile); } protected function tearDown(): void { if (\file_exists($this->pmdLogFile)) { \unlink($this->pmdLogFile); } if (\file_exists($this->expectedPmdLogFile)) { \unlink($this->expectedPmdLogFile); } } public function testSubstitutesDisallowedCharacters(): void { $file1 = new CodeCloneFile($this->testFile1, 8); $file2 = new CodeCloneFile($this->testFile2, 8); $clone = new CodeClone($file1, $file2, 4, 4); $cloneMap = new CodeCloneMap(); $cloneMap->addClone($clone); $this->pmdLogger->processClones($cloneMap); $this->assertXmlFileEqualsXmlFile( $this->expectedPmdLogFile, $this->pmdLogFile ); } } phpcpd-4.1.0/tests/Log/_files/000077500000000000000000000000001334776064700161375ustar00rootroot00000000000000phpcpd-4.1.0/tests/Log/_files/pmd_expected.xml000066400000000000000000000004401334776064700213200ustar00rootroot00000000000000 function getAsciiEscapeChar() { return "�"; } phpcpd-4.1.0/tests/Log/_files/with_ascii_escape.php000066400000000000000000000002121334776064700223060ustar00rootroot00000000000000. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Manuel Pichler nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @version SVN: $Id$ */ /** * Simple math class. * */ class PhpUnderControl_Example_Math { /** * Adds the two given values. * * @param integer $v1 Value one. * @param integer $v2 Value two. * * @return integer. */ public function add($v1, $v2) { return ($v1 + $v2); } /** * Subtract param two from param one * * @param integer $v1 Value one. * @param integer $v2 Value two. * * @return integer. */ public function sub($v1, $v2) { return ($v1 - $v2); } /** * Not tested method that should be visible with low coverage. */ public function div($v1, $v2) { $v3 = $v1 / ($v2 + $v1); if ($v3 > 14) { $v4 = 0; for ($i = 0; $i < $v3; $i++) { $v4 += ($v2 * $i); } } $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3)); $v6 = ($v1 * $v2 * $v3 * $v4 * $v5); $d = array($v1, $v2, $v3, $v4, $v5, $v6); $v7 = 1; for ($i = 0; $i < $v6; $i++) { shuffle( $d ); $v7 = $v7 + $i * end($d); } $v8 = $v7; foreach ( $d as $x ) { $v8 *= $x; } $v3 = $v1 / ($v2 + $v1); if ($v3 > 14) { $v4 = 0; for ($i = 0; $i < $v3; $i++) { $v4 += ($v2 * $i); } } $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3)); $v6 = ($v1 * $v2 * $v3 * $v4 * $v5); $d = array($v1, $v2, $v3, $v4, $v5, $v6); $v7 = 1; for ($i = 0; $i < $v6; $i++) { shuffle( $d ); $v7 = $v7 + $i * end($d); } $v8 = $v7; foreach ( $d as $x ) { $v8 *= $x; } return $v8; } /** * Simple copy for cpd detection. */ public function complex($v1, $v2) { $v3 = $v1 / ($v2 + $v1); if ($v3 > 14) { $v4 = 0; for ($i = 0; $i < $v3; $i++) { $v4 += ($v2 * $i); } } $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3)); $v6 = ($v1 * $v2 * $v3 * $v4 * $v5); $d = array($v1, $v2, $v3, $v4, $v5, $v6); $v7 = 1; for ($i = 0; $i < $v6; $i++) { shuffle( $d ); $v7 = $v7 + $i * end( $d ); } $v8 = $v7; foreach ( $d as $x ) { $v8 *= $x; } $v3 = $v1 / ($v2 + $v1); if ($v3 > 14) { $v4 = 0; for ($i = 0; $i < $v3; $i++) { $v4 += ($v2 * $i); } } $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3)); $v6 = ($v1 * $v2 * $v3 * $v4 * $v5); $d = array($v1, $v2, $v3, $v4, $v5, $v6); $v7 = 1; for ($i = 0; $i < $v6; $i++) { shuffle( $d ); $v7 = $v7 + $i * end($d); } $v8 = $v7; foreach ( $d as $x ) { $v8 *= $x; } return $v8; } } phpcpd-4.1.0/tests/_files/a.php000066400000000000000000000006011334776064700163440ustar00rootroot00000000000000