pax_global_header00006660000000000000000000000064137677320160014526gustar00rootroot0000000000000052 comment=e59481d2f29a73492165389f77c6800f1feec345 phpcpd-6.0.3/000077500000000000000000000000001376773201600130125ustar00rootroot00000000000000phpcpd-6.0.3/.github/000077500000000000000000000000001376773201600143525ustar00rootroot00000000000000phpcpd-6.0.3/.github/FUNDING.yml000066400000000000000000000000321376773201600161620ustar00rootroot00000000000000github: sebastianbergmann phpcpd-6.0.3/.github/workflows/000077500000000000000000000000001376773201600164075ustar00rootroot00000000000000phpcpd-6.0.3/.github/workflows/ci.yml000066400000000000000000000040531376773201600175270ustar00rootroot00000000000000# https://help.github.com/en/categories/automating-your-workflow-with-github-actions on: - "pull_request" - "push" name: "CI" jobs: coding-guidelines: name: "Coding Guidelines" runs-on: "ubuntu-latest" steps: - name: "Checkout" uses: "actions/checkout@v2" - name: "Run friendsofphp/php-cs-fixer" run: "./tools/php-cs-fixer fix --diff-format=udiff --dry-run --show-progress=dots --using-cache=no --verbose" type-checker: name: "Type Checker" runs-on: "ubuntu-latest" steps: - name: "Checkout" uses: "actions/checkout@v2" - name: "Update dependencies with composer" run: "./tools/composer update --no-ansi --no-interaction --no-progress" - name: "Run vimeo/psalm" run: "./tools/psalm --config=.psalm/config.xml --no-progress --shepherd --show-info=false --stats" tests: name: "Tests" runs-on: "ubuntu-latest" strategy: matrix: php-version: - "7.3" - "7.4" - "8.0" - "8.1" steps: - name: "Checkout" uses: "actions/checkout@v2" - name: "Install PHP with extensions" uses: "shivammathur/setup-php@v2" with: php-version: "${{ matrix.php-version }}" coverage: "pcov" - name: "Cache dependencies installed with composer" uses: "actions/cache@v1" with: path: "~/.composer/cache" key: "php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}" restore-keys: "php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-" - name: "Install dependencies with composer" run: "./tools/composer update --no-ansi --no-interaction --no-progress" - name: "Run tests with phpunit/phpunit" run: "./tools/phpunit --coverage-clover=coverage.xml" - name: "Send code coverage report to Codecov.io" env: CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" run: "bash <(curl -s https://codecov.io/bash) || true" phpcpd-6.0.3/.gitignore000066400000000000000000000001411376773201600147760ustar00rootroot00000000000000/build/phar /build/*.phar* /.idea /vendor /.php_cs /.php_cs.cache /.phpunit.cache /composer.lock phpcpd-6.0.3/.phive/000077500000000000000000000000001376773201600142035ustar00rootroot00000000000000phpcpd-6.0.3/.phive/phars.xml000066400000000000000000000011071376773201600160410ustar00rootroot00000000000000 phpcpd-6.0.3/.php_cs.dist000066400000000000000000000206461376773201600152410ustar00rootroot00000000000000 For the full copyright and license information, please view the LICENSE file that was distributed with this source code. EOF; $finder = PhpCsFixer\Finder::create() ->files() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests/unit'); return PhpCsFixer\Config::create() ->setFinder($finder) ->setRiskyAllowed(true) ->setRules([ 'align_multiline_comment' => true, 'array_indentation' => true, 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => [ 'operators' => [ '=' => 'align_single_space_minimal', '=>' => 'align_single_space_minimal', ], ], 'blank_line_after_namespace' => true, 'blank_line_before_statement' => [ 'statements' => [ 'break', 'continue', 'declare', 'default', 'die', 'do', 'exit', 'for', 'foreach', 'goto', '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'], 'constant_case' => true, 'declare_equal_normalize' => ['space' => 'none'], 'declare_strict_types' => true, 'dir_constant' => true, 'elseif' => true, 'encoding' => true, 'explicit_indirect_variable' => true, 'explicit_string_variable' => true, 'full_opening_tag' => true, 'fully_qualified_strict_types' => true, 'function_declaration' => true, 'global_namespace_import' => [ 'import_classes' => true, 'import_constants' => true, 'import_functions' => true, ], 'header_comment' => ['header' => $header, 'separate' => 'none'], 'heredoc_to_nowdoc' => true, 'increment_style' => [ 'style' => PhpCsFixer\Fixer\Operator\IncrementStyleFixer::STYLE_POST, ], 'indentation_type' => true, 'is_null' => true, 'line_ending' => true, 'list_syntax' => ['syntax' => 'short'], 'logical_operators' => true, 'lowercase_keywords' => true, 'lowercase_static_reference' => true, 'magic_constant_casing' => true, 'magic_method_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' => false, 'native_function_casing' => false, 'native_function_invocation' => false, 'native_function_type_declaration_casing' => 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_around_offset' => true, 'no_spaces_inside_parenthesis' => true, 'no_superfluous_elseif' => true, 'no_superfluous_phpdoc_tags' => [ 'allow_mixed' => 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' => [ 'imports_order' => [ PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_CONST, PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_FUNCTION, PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_CLASS, ] ], 'ordered_interfaces' => [ 'direction' => 'ascend', 'order' => 'alpha', ], 'phpdoc_add_missing_param_annotation' => false, '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_summary' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_types' => ['groups' => ['simple', 'meta']], '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, 'self_static_accessor' => true, 'semicolon_after_instruction' => true, 'set_type_to_cast' => true, 'short_scalar_cast' => true, 'simple_to_complex_string_variable' => true, 'simplified_null_return' => false, 'single_blank_line_at_eof' => true, 'single_import_per_statement' => true, 'single_line_after_imports' => true, 'single_quote' => true, 'standardize_not_equals' => true, 'strict_param' => true, 'ternary_to_null_coalescing' => true, 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'visibility_required' => [ 'elements' => [ 'const', 'method', 'property', ], ], 'void_return' => true, 'whitespace_after_comma_in_array' => true, ]); phpcpd-6.0.3/.psalm/000077500000000000000000000000001376773201600142045ustar00rootroot00000000000000phpcpd-6.0.3/.psalm/baseline.xml000066400000000000000000000041541376773201600165140ustar00rootroot00000000000000 $directories $exclude $suffixes $directories $pmdCpdXmlLogfile $argv $option[0] $option[1] $option[1] $option[1] $option[1] $option[1] $directories $exclude[] $option $pmdCpdXmlLogfile $suffixes[] $indent $indent $file $file $lastToken + 1 - $firstToken $this->hashes[$firstHash] $firstHash $firstHash $firstHash $firstToken processClones phpcpd-6.0.3/.psalm/config.xml000066400000000000000000000007101376773201600161710ustar00rootroot00000000000000 phpcpd-6.0.3/ChangeLog.md000066400000000000000000000062631376773201600151720ustar00rootroot00000000000000# Changes in PHPCPD All notable changes in PHPCPD are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. ## [6.0.3] - 2020-12-07 ### Changed * Changed PHP version constraint in `composer.json` from `^7.3` to `>=7.3` ## [6.0.2] - 2020-08-18 ### Fixed * [#187](https://github.com/sebastianbergmann/phpcpd/issues/187): Exclude arguments are being handled as prefixes ## [6.0.1] - 2020-08-13 ### Fixed * The `--verbose` CLI option had no effect ## [6.0.0] - 2020-08-13 ### Removed * The `--names` CLI option has been removed; use the `--suffix` CLI option instead * The `--names-exclude` CLI option has been removed; use the `--exclude` CLI option instead * The `--regexps-exclude` CLI option has been removed * The `--progress` CLI option has been removed ## [5.0.2] - 2020-02-22 ### Changed * Require `sebastian/version` version 3 and `phpunit/php-timer` version 3 to allow Composer-based installation alongside `phploc/phploc` version 6 and `phpunit/phpunit` version 9 ## [5.0.1] - 2020-02-20 ### Fixed * [#181](https://github.com/sebastianbergmann/phpcpd/issues/181): `--min-lines`, `--min-tokens`, and `--fuzzy` commandline options do not work ## [5.0.0] - 2020-02-20 ### Removed * Removed support for PHP versions older than PHP 7.3 ## [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 * Removed support for PHP versions older than PHP 7.1 ## [3.0.1] - 2017-11-16 ### Fixed * [#147](https://github.com/sebastianbergmann/phpcpd/issues/147): Wrong exit code when no files were found to be scanned * [#152](https://github.com/sebastianbergmann/phpcpd/issues/152): Version requirement for `sebastian/version` is too strict ## [3.0.0] - 2017-02-05 ### Added * [#90](https://github.com/sebastianbergmann/phpcpd/pull/90): The PMD logger now replaces all characters that are invalid XML with `U+FFFD` * [#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 * Removed support for PHP versions older than PHP 5.6 [6.0.3]: https://github.com/sebastianbergmann/phpcpd/compare/6.0.2...6.0.3 [6.0.2]: https://github.com/sebastianbergmann/phpcpd/compare/6.0.1...6.0.2 [6.0.1]: https://github.com/sebastianbergmann/phpcpd/compare/6.0.0...6.0.1 [6.0.0]: https://github.com/sebastianbergmann/phpcpd/compare/5.0.2...6.0.0 [5.0.2]: https://github.com/sebastianbergmann/phpcpd/compare/5.0.1...5.0.2 [5.0.1]: https://github.com/sebastianbergmann/phpcpd/compare/5.0.0...5.0.1 [5.0.0]: https://github.com/sebastianbergmann/phpcpd/compare/4.1.0...5.0.0 [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-6.0.3/LICENSE000066400000000000000000000030051376773201600140150ustar00rootroot00000000000000phpcpd Copyright (c) 2009-2020, 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-6.0.3/README.md000066400000000000000000000024111376773201600142670ustar00rootroot00000000000000# PHP Copy/Paste Detector (PHPCPD) `phpcpd` is a Copy/Paste Detector (CPD) for PHP code. ## Installation This tool is distributed as a [PHP Archive (PHAR)](https://php.net/phar): ```bash $ wget https://phar.phpunit.de/phpcpd.phar $ php phpcpd.phar --version ``` Using [Phive](https://phar.io/) is the recommended way for managing the tool dependencies of your project: ```bash $ phive install phpcpd $ ./tools/phpcpd --version ``` **[It is not recommended to use Composer to download and install this tool.](https://twitter.com/s_bergmann/status/999635212723212288)** ## Usage Example ``` $ php phpcpd.phar --fuzzy wordpress-5.5 phpcpd 6.0.0 by Sebastian Bergmann. Found 121 clones with 8137 duplicated lines in 69 files: - /home/sb/wordpress-5.5/wp-includes/sodium_compat/src/Core/Curve25519/H.php:19-1466 (1447 lines) /home/sb/wordpress-5.5/wp-includes/sodium_compat/src/Core32/Curve25519/H.php:19-1466 . . . - /home/sb/wordpress-5.5/wp-includes/sodium_compat/src/Core32/Curve25519.php:879-889 (10 lines) /home/sb/wordpress-5.5/wp-includes/sodium_compat/src/Core32/Curve25519.php:1072-1082 1.82% duplicated lines out of 446676 total lines of code. Average size of duplication is 67 lines, largest clone has 1447 of lines Time: 00:02.980, Memory: 318.00 MB ``` phpcpd-6.0.3/build.xml000066400000000000000000000107161376773201600146400ustar00rootroot00000000000000 phpcpd-6.0.3/build/000077500000000000000000000000001376773201600141115ustar00rootroot00000000000000phpcpd-6.0.3/build/phar-autoload.php.in000066400000000000000000000024121376773201600177660ustar00rootroot00000000000000#!/usr/bin/env php ')) { fwrite( STDERR, sprintf( 'This version of PHPCPD requires PHP 7.3 (or later).' . 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; } exit((new \SebastianBergmann\PHPCPD\Application)->run($_SERVER['argv'])); __HALT_COMPILER(); phpcpd-6.0.3/build/phar-manifest.php000077500000000000000000000013421376773201600173630ustar00rootroot00000000000000#!/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-6.0.3/composer.json000066400000000000000000000021021376773201600155270ustar00rootroot00000000000000{ "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.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "minimum-stability": "dev", "prefer-stable": true, "require": { "php": ">=7.3", "ext-dom": "*", "sebastian/cli-parser": "^1.0", "sebastian/version": "^3.0", "phpunit/php-file-iterator": "^3.0", "phpunit/php-timer": "^5.0" }, "autoload": { "classmap": [ "src/" ] }, "bin": [ "phpcpd" ], "extra": { "branch-alias": { "dev-master": "6.0-dev" } } } phpcpd-6.0.3/phpcpd000077500000000000000000000021721376773201600142200ustar00rootroot00000000000000#!/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.3.0', PHP_VERSION, '>')) { fwrite( STDERR, sprintf( 'This version of PHPCPD requires PHP 7.3 (or later).' . PHP_EOL . 'You are using PHP %s%s.' . PHP_EOL, PHP_VERSION, defined('PHP_BINARY') ? ' (' . PHP_BINARY . ')' : '' ) ); die(1); } $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 ); } exit((new \SebastianBergmann\PHPCPD\Application)->run($_SERVER['argv'])); phpcpd-6.0.3/phpunit.xml000066400000000000000000000017241376773201600152270ustar00rootroot00000000000000 tests src phpcpd-6.0.3/src/000077500000000000000000000000001376773201600136015ustar00rootroot00000000000000phpcpd-6.0.3/src/CLI/000077500000000000000000000000001376773201600142105ustar00rootroot00000000000000phpcpd-6.0.3/src/CLI/Application.php000066400000000000000000000061071376773201600171700ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use const PHP_EOL; use function count; use function printf; use SebastianBergmann\FileIterator\Facade; use SebastianBergmann\PHPCPD\Detector\Detector; use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy; use SebastianBergmann\PHPCPD\Log\PMD; use SebastianBergmann\PHPCPD\Log\Text; use SebastianBergmann\Timer\ResourceUsageFormatter; use SebastianBergmann\Timer\Timer; use SebastianBergmann\Version; final class Application { private const VERSION = '6.0.3'; public function run(array $argv): int { $this->printVersion(); try { $arguments = (new ArgumentsBuilder)->build($argv); } catch (Exception $e) { print PHP_EOL . $e->getMessage() . PHP_EOL; return 1; } if ($arguments->version()) { return 0; } print PHP_EOL; if ($arguments->help()) { $this->help(); return 0; } $files = (new Facade)->getFilesAsArray( $arguments->directories(), $arguments->suffixes(), '', $arguments->exclude() ); if (empty($files)) { print 'No files found to scan' . PHP_EOL; return 1; } $strategy = new DefaultStrategy; $timer = new Timer; $timer->start(); $clones = (new Detector($strategy))->copyPasteDetection( $files, $arguments->linesThreshold(), $arguments->tokensThreshold(), $arguments->fuzzy() ); (new Text)->printResult($clones, $arguments->verbose()); if ($arguments->pmdCpdXmlLogfile()) { (new PMD($arguments->pmdCpdXmlLogfile()))->processClones($clones); } print (new ResourceUsageFormatter)->resourceUsage($timer->stop()) . PHP_EOL; return count($clones) > 0 ? 1 : 0; } private function printVersion(): void { printf( 'phpcpd %s by Sebastian Bergmann.' . PHP_EOL, (new Version(self::VERSION, dirname(__DIR__)))->getVersion() ); } private function help(): void { print <<<'EOT' Usage: phpcpd [options] Options for selecting files: --suffix Include files with names ending in in the analysis (default: .php; can be given multiple times) --exclude Exclude files with in their path from the analysis (can be given multiple times) Options for analysing files: --fuzzy Fuzz variable names --min-lines Minimum number of identical lines (default: 5) --min-tokens Minimum number of identical tokens (default: 70) Options for report generation: --log-pmd Write log in PMD-CPD XML format to EOT; } } phpcpd-6.0.3/src/CLI/Arguments.php000066400000000000000000000051401376773201600166660ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; final class Arguments { /** * @psalm-var list */ private $directories; /** * @psalm-var list */ private $suffixes; /** * @psalm-var list */ private $exclude; /** * @var ?string */ private $pmdCpdXmlLogfile; /** * @var int */ private $linesThreshold; /** * @var int */ private $tokensThreshold; /** * @var bool */ private $fuzzy; /** * @var bool */ private $verbose; /** * @var bool */ private $help; /** * @var bool */ private $version; public function __construct(array $directories, array $suffixes, array $exclude, ?string $pmdCpdXmlLogfile, int $linesThreshold, int $tokensThreshold, bool $fuzzy, bool $verbose, bool $help, bool $version) { $this->directories = $directories; $this->suffixes = $suffixes; $this->exclude = $exclude; $this->pmdCpdXmlLogfile = $pmdCpdXmlLogfile; $this->linesThreshold = $linesThreshold; $this->tokensThreshold = $tokensThreshold; $this->fuzzy = $fuzzy; $this->verbose = $verbose; $this->help = $help; $this->version = $version; } /** * @psalm-return list */ public function directories(): array { return $this->directories; } /** * @psalm-return list */ public function suffixes(): array { return $this->suffixes; } /** * @psalm-return list */ public function exclude(): array { return $this->exclude; } public function pmdCpdXmlLogfile(): ?string { return $this->pmdCpdXmlLogfile; } public function linesThreshold(): int { return $this->linesThreshold; } public function tokensThreshold(): int { return $this->tokensThreshold; } public function fuzzy(): bool { return $this->fuzzy; } public function verbose(): bool { return $this->verbose; } public function help(): bool { return $this->help; } public function version(): bool { return $this->version; } } phpcpd-6.0.3/src/CLI/ArgumentsBuilder.php000066400000000000000000000061261376773201600202020ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use SebastianBergmann\CliParser\Exception as CliParserException; use SebastianBergmann\CliParser\Parser as CliParser; final class ArgumentsBuilder { /** * @throws ArgumentsBuilderException */ public function build(array $argv): Arguments { try { $options = (new CliParser)->parse( $argv, 'hv', [ 'suffix=', 'exclude=', 'log-pmd=', 'fuzzy', 'min-lines=', 'min-tokens=', 'verbose', 'help', 'version', ] ); } catch (CliParserException $e) { throw new ArgumentsBuilderException( $e->getMessage(), (int) $e->getCode(), $e ); } $directories = $options[1]; $exclude = []; $suffixes = ['.php']; $pmdCpdXmlLogfile = null; $linesThreshold = 5; $tokensThreshold = 70; $fuzzy = false; $verbose = false; $help = false; $version = false; foreach ($options[0] as $option) { switch ($option[0]) { case '--suffix': $suffixes[] = $option[1]; break; case '--exclude': $exclude[] = $option[1]; break; case '--log-pmd': $pmdCpdXmlLogfile = $option[1]; break; case '--fuzzy': $fuzzy = true; break; case '--min-lines': $linesThreshold = (int) $option[1]; break; case '--min-tokens': $tokensThreshold = (int) $option[1]; break; case '--verbose': $verbose = true; break; case 'h': case '--help': $help = true; break; case 'v': case '--version': $version = true; break; } } if (empty($options[1]) && !$help && !$version) { throw new ArgumentsBuilderException( 'No directory specified' ); } return new Arguments( $directories, $suffixes, $exclude, $pmdCpdXmlLogfile, $linesThreshold, $tokensThreshold, $fuzzy, $verbose, $help, $version, ); } } phpcpd-6.0.3/src/CodeClone.php000066400000000000000000000043631376773201600161530ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use function array_map; use function array_slice; use function current; use function file; use function implode; use function md5; final class CodeClone { /** * @var int */ private $numberOfLines; /** * @var int */ private $numberOfTokens; /** * @var CodeCloneFile[] */ private $files = []; /** * @var string */ private $id; /** * @var string */ private $lines = ''; public function __construct(CodeCloneFile $fileA, CodeCloneFile $fileB, int $numberOfLines, int $numberOfTokens) { $this->add($fileA); $this->add($fileB); $this->numberOfLines = $numberOfLines; $this->numberOfTokens = $numberOfTokens; $this->id = md5($this->lines()); } public function add(CodeCloneFile $file): void { $id = $file->id(); if (!isset($this->files[$id])) { $this->files[$id] = $file; } } /** * @return CodeCloneFile[] */ public function files(): array { return $this->files; } public function lines($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->name()), $file->startLine() - 1, $this->numberOfLines ) ) ); } return $this->lines; } public function id(): string { return $this->id; } public function numberOfLines(): int { return $this->numberOfLines; } public function numberOfTokens(): int { return $this->numberOfTokens; } } phpcpd-6.0.3/src/CodeCloneFile.php000066400000000000000000000016711376773201600167520ustar00rootroot00000000000000 * * 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 id(): string { return $this->id; } public function name(): string { return $this->name; } public function startLine(): int { return $this->startLine; } } phpcpd-6.0.3/src/CodeCloneMap.php000066400000000000000000000057301376773201600166100ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use function count; use function max; use function sprintf; use Countable; use IteratorAggregate; final class CodeCloneMap implements Countable, IteratorAggregate { /** * @var CodeClone[] */ private $clones = []; /** * @var CodeClone[] */ private $clonesById = []; /** * @var int */ private $numberOfDuplicatedLines = 0; /** * @var int */ private $numberOfLines = 0; /** * @var int */ private $largestCloneSize = 0; /** * @var array */ private $filesWithClones = []; public function add(CodeClone $clone): void { $id = $clone->id(); if (!isset($this->clonesById[$id])) { $this->clones[] = $clone; $this->clonesById[$id] = $clone; } else { $existClone = $this->clonesById[$id]; foreach ($clone->files() as $file) { $existClone->add($file); } } $this->numberOfDuplicatedLines += $clone->numberOfLines() * (count($clone->files()) - 1); foreach ($clone->files() as $file) { if (!isset($this->filesWithClones[$file->name()])) { $this->filesWithClones[$file->name()] = true; } } $this->largestCloneSize = max($this->largestCloneSize, $clone->numberOfLines()); } /** * @return CodeClone[] */ public function clones(): array { return $this->clones; } public function percentage(): string { if ($this->numberOfLines > 0) { $percent = ($this->numberOfDuplicatedLines / $this->numberOfLines) * 100; } else { $percent = 100; } return sprintf('%01.2F%%', $percent); } public function numberOfLines(): int { return $this->numberOfLines; } public function addToNumberOfLines(int $numberOfLines): void { $this->numberOfLines += $numberOfLines; } public function count(): int { return count($this->clones); } public function numberOfFilesWithClones(): int { return count($this->filesWithClones); } public function numberOfDuplicatedLines(): int { return $this->numberOfDuplicatedLines; } public function getIterator(): CodeCloneMapIterator { return new CodeCloneMapIterator($this); } public function isEmpty(): bool { return empty($this->clones); } public function averageSize(): float { return $this->numberOfDuplicatedLines() / $this->count(); } public function largestSize(): int { return $this->largestCloneSize; } } phpcpd-6.0.3/src/CodeCloneMapIterator.php000066400000000000000000000025371376773201600203240ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use function array_reverse; use function count; use function usort; use Iterator; final class CodeCloneMapIterator implements Iterator { /** * @var CodeClone[] */ private $clones = []; /** * @var int */ private $position = 0; public function __construct(CodeCloneMap $clones) { $this->clones = $clones->clones(); usort( $this->clones, static function (CodeClone $a, CodeClone $b): int { return $a->numberOfLines() <=> $b->numberOfLines(); } ); $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-6.0.3/src/Detector/000077500000000000000000000000001376773201600153525ustar00rootroot00000000000000phpcpd-6.0.3/src/Detector/Detector.php000066400000000000000000000021701376773201600176340ustar00rootroot00000000000000 * * 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; final class Detector { /** * @var AbstractStrategy */ private $strategy; public function __construct(AbstractStrategy $strategy) { $this->strategy = $strategy; } 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 ); } return $result; } } phpcpd-6.0.3/src/Detector/Strategy/000077500000000000000000000000001376773201600171545ustar00rootroot00000000000000phpcpd-6.0.3/src/Detector/Strategy/AbstractStrategy.php000066400000000000000000000024261376773201600231570ustar00rootroot00000000000000 * * 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 const T_CLOSE_TAG; use const T_COMMENT; use const T_DOC_COMMENT; use const T_INLINE_HTML; use const T_NS_SEPARATOR; use const T_OPEN_TAG; use const T_OPEN_TAG_WITH_ECHO; use const T_USE; use const T_WHITESPACE; use SebastianBergmann\PHPCPD\CodeCloneMap; abstract class AbstractStrategy { /** * @psalm-var array */ 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, ]; /** * @psalm-var array */ protected $hashes = []; abstract public function processFile(string $file, int $minLines, int $minTokens, CodeCloneMap $result, bool $fuzzy = false): void; } phpcpd-6.0.3/src/Detector/Strategy/DefaultStrategy.php000066400000000000000000000126451376773201600230040ustar00rootroot00000000000000 * * 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 const T_VARIABLE; use function array_keys; use function chr; use function count; use function crc32; use function file_get_contents; use function is_array; use function md5; use function pack; use function substr; use function substr_count; use function token_get_all; use SebastianBergmann\PHPCPD\CodeClone; use SebastianBergmann\PHPCPD\CodeCloneFile; use SebastianBergmann\PHPCPD\CodeCloneMap; final 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->addToNumberOfLines(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->add( 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->add( new CodeClone( new CodeCloneFile($fileA, $firstLineA), new CodeCloneFile($file, $firstRealLine), $realNumLines, $lastToken + 1 - $firstToken ) ); } } } } phpcpd-6.0.3/src/Exceptions/000077500000000000000000000000001376773201600157225ustar00rootroot00000000000000phpcpd-6.0.3/src/Exceptions/ArgumentsBuilderException.php000066400000000000000000000006521376773201600235710ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use RuntimeException; final class ArgumentsBuilderException extends RuntimeException implements Exception { } phpcpd-6.0.3/src/Exceptions/Exception.php000066400000000000000000000005651376773201600203770ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD; use Throwable; interface Exception extends Throwable { } phpcpd-6.0.3/src/Log/000077500000000000000000000000001376773201600143225ustar00rootroot00000000000000phpcpd-6.0.3/src/Log/AbstractXmlLogger.php000066400000000000000000000051221376773201600204170ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use const ENT_COMPAT; use function file_put_contents; use function function_exists; use function htmlspecialchars; use function mb_convert_encoding; use function ord; use function preg_replace; use function strlen; use function utf8_encode; use DOMDocument; use SebastianBergmann\PHPCPD\CodeCloneMap; abstract class AbstractXmlLogger { /** * @var DOMDocument */ protected $document; /** * @var string */ private $filename; public function __construct(string $filename) { $this->document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->filename = $filename; } abstract public function processClones(CodeCloneMap $clones); protected function flush(): void { file_put_contents($this->filename, $this->document->saveXML()); } 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; } protected function isUtf8(string $string): bool { $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; } protected function escapeForXml(string $string): string { $string = $this->convertToUtf8($string); $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-6.0.3/src/Log/PMD.php000066400000000000000000000030511376773201600154520ustar00rootroot00000000000000 * * 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 { /** @noinspection UnusedFunctionResultInspection */ 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', (string) $clone->numberOfLines()); $duplication->setAttribute('tokens', (string) $clone->numberOfTokens()); foreach ($clone->files() as $codeCloneFile) { $file = $duplication->appendChild( $this->document->createElement('file') ); $file->setAttribute('path', $codeCloneFile->name()); $file->setAttribute('line', (string) $codeCloneFile->startLine()); } $duplication->appendChild( $this->document->createElement( 'codefragment', $this->escapeForXml($clone->lines()) ) ); } $this->flush(); } } phpcpd-6.0.3/src/Log/Text.php000066400000000000000000000037271376773201600157700ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use const PHP_EOL; use function count; use function printf; use SebastianBergmann\PHPCPD\CodeCloneMap; final class Text { public function printResult(CodeCloneMap $clones, bool $verbose): void { if (count($clones) > 0) { printf( 'Found %d clones with %d duplicated lines in %d files:' . PHP_EOL . PHP_EOL, count($clones), $clones->numberOfDuplicatedLines(), $clones->numberOfFilesWithClones() ); } foreach ($clones as $clone) { $firstOccurrence = true; foreach ($clone->files() as $file) { printf( ' %s%s:%d-%d%s' . PHP_EOL, $firstOccurrence ? '- ' : ' ', $file->name(), $file->startLine(), $file->startLine() + $clone->numberOfLines(), $firstOccurrence ? ' (' . $clone->numberOfLines() . ' lines)' : '' ); $firstOccurrence = false; } if ($verbose) { print PHP_EOL . $clone->lines(' '); } print PHP_EOL; } if ($clones->isEmpty()) { print 'No clones found.' . PHP_EOL . PHP_EOL; return; } printf( '%s duplicated lines out of %d total lines of code.' . PHP_EOL . 'Average size of duplication is %d lines, largest clone has %d of lines' . PHP_EOL . PHP_EOL, $clones->percentage(), $clones->numberOfLines(), $clones->averageSize(), $clones->largestSize() ); } } phpcpd-6.0.3/tests/000077500000000000000000000000001376773201600141545ustar00rootroot00000000000000phpcpd-6.0.3/tests/fixture/000077500000000000000000000000001376773201600156425ustar00rootroot00000000000000phpcpd-6.0.3/tests/fixture/Math.php000066400000000000000000000114211376773201600172430ustar00rootroot00000000000000. * 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-6.0.3/tests/fixture/a.php000066400000000000000000000006011376773201600165700ustar00rootroot00000000000000 function getAsciiEscapeChar() { return "�"; } phpcpd-6.0.3/tests/fixture/with_ascii_escape.php000066400000000000000000000002121376773201600220110ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Detector; use function current; use function next; use function sort; 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 */ final class DetectorTest extends TestCase { /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testDetectingSimpleClonesWorks(string $strategy): void { $clones = (new Detector(new $strategy))->copyPasteDetection( [__DIR__ . '/../fixture/Math.php'] ); $clones = $clones->clones(); $files = $clones[0]->files(); $file = current($files); $this->assertSame(__DIR__ . '/../fixture/Math.php', $file->name()); $this->assertSame(75, $file->startLine()); $file = next($files); $this->assertSame(__DIR__ . '/../fixture/Math.php', $file->name()); $this->assertSame(139, $file->startLine()); $this->assertSame(59, $clones[0]->numberOfLines()); $this->assertSame(136, $clones[0]->numberOfTokens()); $this->assertSame( ' 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]->lines() ); } /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testDetectingExactDuplicateFilesWorks(string $strategy): void { $clones = (new Detector(new $strategy))->copyPasteDetection( [ __DIR__ . '/../fixture/a.php', __DIR__ . '/../fixture/b.php', ], 20, 60 ); $clones = $clones->clones(); $files = $clones[0]->files(); $file = current($files); $this->assertCount(1, $clones); $this->assertSame(__DIR__ . '/../fixture/a.php', $file->name()); $this->assertSame(4, $file->startLine()); $file = next($files); $this->assertSame(__DIR__ . '/../fixture/b.php', $file->name()); $this->assertSame(4, $file->startLine()); $this->assertSame(20, $clones[0]->numberOfLines()); $this->assertSame(60, $clones[0]->numberOfTokens()); } /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testDetectingClonesInMoreThanTwoFiles(string $strategy): void { $clones = (new Detector(new $strategy))->copyPasteDetection( [ __DIR__ . '/../fixture/a.php', __DIR__ . '/../fixture/b.php', __DIR__ . '/../fixture/c.php', ], 20, 60 ); $clones = $clones->clones(); $files = $clones[0]->files(); sort($files); $file = current($files); $this->assertCount(1, $clones); $this->assertSame(__DIR__ . '/../fixture/a.php', $file->name()); $this->assertSame(4, $file->startLine()); $file = next($files); $this->assertSame(__DIR__ . '/../fixture/b.php', $file->name()); $this->assertSame(4, $file->startLine()); $file = next($files); $this->assertSame(__DIR__ . '/../fixture/c.php', $file->name()); $this->assertSame(4, $file->startLine()); } /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testClonesAreIgnoredIfTheySpanLessTokensThanMinTokens(string $strategy): void { $clones = (new Detector(new $strategy))->copyPasteDetection( [ __DIR__ . '/../fixture/a.php', __DIR__ . '/../fixture/b.php', ], 20, 61 ); $this->assertCount(0, $clones->clones()); } /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testClonesAreIgnoredIfTheySpanLessLinesThanMinLines(string $strategy): void { $clones = (new Detector(new $strategy))->copyPasteDetection( [ __DIR__ . '/../fixture/a.php', __DIR__ . '/../fixture/b.php', ], 21, 60 ); $this->assertCount(0, $clones->clones()); } /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testFuzzyClonesAreFound(string $strategy): void { $clones = (new Detector(new $strategy))->copyPasteDetection( [ __DIR__ . '/../fixture/a.php', __DIR__ . '/../fixture/d.php', ], 5, 20, true ); $this->assertCount(1, $clones->clones()); } /** * @dataProvider strategyProvider * * @psalm-param class-string $strategy */ public function testStripComments(string $strategy): void { $detector = new Detector(new $strategy); $clones = $detector->copyPasteDetection( [ __DIR__ . '/../fixture/e.php', __DIR__ . '/../fixture/f.php', ], 8, 10, true ); $this->assertCount(0, $clones->clones()); $clones = $detector->copyPasteDetection( [ __DIR__ . '/../fixture/e.php', __DIR__ . '/../fixture/f.php', ], 7, 10, true ); $this->assertCount(1, $clones->clones()); } /** * @psalm-return list */ public function strategyProvider(): array { return [ [DefaultStrategy::class], ]; } } phpcpd-6.0.3/tests/unit/PMDTest.php000066400000000000000000000054321376773201600171300ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCPD\Log; use function file_exists; use function file_get_contents; use function file_put_contents; use function strtr; use function sys_get_temp_dir; use function tempnam; use function unlink; 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 */ final 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__ . '/../fixture/with_ascii_escape.php'; $this->testFile2 = __DIR__ . '/../fixture/with_ascii_escape2.php'; $this->pmdLogFile = tempnam(sys_get_temp_dir(), 'pmd'); $this->expectedPmdLogFile = tempnam(sys_get_temp_dir(), 'pmd'); $expectedPmdLogTemplate = __DIR__ . '/../fixture/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->add($clone); $this->pmdLogger->processClones($cloneMap); $this->assertXmlFileEqualsXmlFile( $this->expectedPmdLogFile, $this->pmdLogFile ); } }