pax_global_header 0000666 0000000 0000000 00000000064 13767732016 0014526 g ustar 00root root 0000000 0000000 52 comment=e59481d2f29a73492165389f77c6800f1feec345
phpcpd-6.0.3/ 0000775 0000000 0000000 00000000000 13767732016 0013012 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/.github/ 0000775 0000000 0000000 00000000000 13767732016 0014352 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/.github/FUNDING.yml 0000664 0000000 0000000 00000000032 13767732016 0016162 0 ustar 00root root 0000000 0000000 github: sebastianbergmann
phpcpd-6.0.3/.github/workflows/ 0000775 0000000 0000000 00000000000 13767732016 0016407 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/.github/workflows/ci.yml 0000664 0000000 0000000 00000004053 13767732016 0017527 0 ustar 00root root 0000000 0000000 # 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/.gitignore 0000664 0000000 0000000 00000000141 13767732016 0014776 0 ustar 00root root 0000000 0000000 /build/phar
/build/*.phar*
/.idea
/vendor
/.php_cs
/.php_cs.cache
/.phpunit.cache
/composer.lock
phpcpd-6.0.3/.phive/ 0000775 0000000 0000000 00000000000 13767732016 0014203 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/.phive/phars.xml 0000664 0000000 0000000 00000001107 13767732016 0016041 0 ustar 00root root 0000000 0000000
phpcpd-6.0.3/.php_cs.dist 0000664 0000000 0000000 00000020646 13767732016 0015241 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 13767732016 0014204 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/.psalm/baseline.xml 0000664 0000000 0000000 00000004154 13767732016 0016514 0 ustar 00root root 0000000 0000000
$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.xml 0000664 0000000 0000000 00000000710 13767732016 0016171 0 ustar 00root root 0000000 0000000
phpcpd-6.0.3/ChangeLog.md 0000664 0000000 0000000 00000006263 13767732016 0015172 0 ustar 00root root 0000000 0000000 # 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/LICENSE 0000664 0000000 0000000 00000003005 13767732016 0014015 0 ustar 00root root 0000000 0000000 phpcpd
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.md 0000664 0000000 0000000 00000002411 13767732016 0014267 0 ustar 00root root 0000000 0000000 # 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.xml 0000664 0000000 0000000 00000010716 13767732016 0014640 0 ustar 00root root 0000000 0000000
phpcpd-6.0.3/build/ 0000775 0000000 0000000 00000000000 13767732016 0014111 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/build/phar-autoload.php.in 0000664 0000000 0000000 00000002412 13767732016 0017766 0 ustar 00root root 0000000 0000000 #!/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.php 0000775 0000000 0000000 00000001342 13767732016 0017363 0 ustar 00root root 0000000 0000000 #!/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.json 0000664 0000000 0000000 00000002102 13767732016 0015527 0 ustar 00root root 0000000 0000000 {
"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/phpcpd 0000775 0000000 0000000 00000002172 13767732016 0014220 0 ustar 00root root 0000000 0000000 #!/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.xml 0000664 0000000 0000000 00000001724 13767732016 0015227 0 ustar 00root root 0000000 0000000
tests
src
phpcpd-6.0.3/src/ 0000775 0000000 0000000 00000000000 13767732016 0013601 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/src/CLI/ 0000775 0000000 0000000 00000000000 13767732016 0014210 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/src/CLI/Application.php 0000664 0000000 0000000 00000006107 13767732016 0017170 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000005140 13767732016 0016666 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000006126 13767732016 0020202 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000004363 13767732016 0016153 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000001671 13767732016 0016752 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000005730 13767732016 0016610 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000002537 13767732016 0020324 0 ustar 00root root 0000000 0000000
*
* 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/ 0000775 0000000 0000000 00000000000 13767732016 0015352 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/src/Detector/Detector.php 0000664 0000000 0000000 00000002170 13767732016 0017634 0 ustar 00root root 0000000 0000000
*
* 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/ 0000775 0000000 0000000 00000000000 13767732016 0017154 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/src/Detector/Strategy/AbstractStrategy.php 0000664 0000000 0000000 00000002426 13767732016 0023157 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000012645 13767732016 0023004 0 ustar 00root root 0000000 0000000
*
* 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/ 0000775 0000000 0000000 00000000000 13767732016 0015722 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/src/Exceptions/ArgumentsBuilderException.php 0000664 0000000 0000000 00000000652 13767732016 0023571 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000000565 13767732016 0020377 0 ustar 00root root 0000000 0000000
*
* 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/ 0000775 0000000 0000000 00000000000 13767732016 0014322 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/src/Log/AbstractXmlLogger.php 0000664 0000000 0000000 00000005122 13767732016 0020417 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000003051 13767732016 0015452 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000003727 13767732016 0015770 0 ustar 00root root 0000000 0000000
*
* 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/ 0000775 0000000 0000000 00000000000 13767732016 0014154 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/tests/fixture/ 0000775 0000000 0000000 00000000000 13767732016 0015642 5 ustar 00root root 0000000 0000000 phpcpd-6.0.3/tests/fixture/Math.php 0000664 0000000 0000000 00000011421 13767732016 0017243 0 ustar 00root root 0000000 0000000 .
* 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.php 0000664 0000000 0000000 00000000601 13767732016 0016570 0 ustar 00root root 0000000 0000000
function getAsciiEscapeChar()
{
return "�";
}
phpcpd-6.0.3/tests/fixture/with_ascii_escape.php 0000664 0000000 0000000 00000000212 13767732016 0022011 0 ustar 00root root 0000000 0000000
*
* 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.php 0000664 0000000 0000000 00000005432 13767732016 0017130 0 ustar 00root root 0000000 0000000
*
* 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
);
}
}