From ab0b1548586b593e074414012a0df1c0c5fed8b4 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 24 Apr 2023 17:17:20 +0200 Subject: [PATCH 1/5] Drop BetterReflection to speed up analysis --- README.md | 21 +- bin/detect-collisions | 23 +- composer.json | 6 +- composer.lock | 338 +++++-------------- phpcs.xml.dist | 12 +- phpstan.neon.dist | 16 +- src/FileParsingException.php | 16 + src/InvalidPathProvidedException.php | 5 +- src/NameCollisionDetector.php | 234 +++++++------ tests/NameCollisionDetectorTest.php | 89 ++--- tests/data/parse-failure/file1.php | 3 + tests/{ => data}/sample-collisions/file1.php | 0 tests/{ => data}/sample-collisions/file2.php | 0 13 files changed, 299 insertions(+), 464 deletions(-) create mode 100644 src/FileParsingException.php create mode 100644 tests/data/parse-failure/file1.php rename tests/{ => data}/sample-collisions/file1.php (100%) rename tests/{ => data}/sample-collisions/file2.php (100%) diff --git a/README.md b/README.md index c6a9bbc..1475f54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Name collision detector -Simple tool which allows you to detect if there are no classes/functions/constants defined multiple times within the same namespace. +Simple tool which allows you to detect if there are no classes (or interfaces or traits) defined multiple times within the same namespace. Non-zero exit code is returned when any duplicate is found. ## Installation: @@ -10,26 +10,18 @@ composer require --dev shipmonk/name-collision-detector ``` ## Usage: -Check duplicate classes, constants and functions: +Check duplicate classes: ```sh vendor/bin/detect-collisions dir1 dir2 dir3 ``` -Or you can select what to check: - -```sh -vendor/bin/detect-collisions --classes src tests # check only duplicate classes -vendor/bin/detect-collisions --functions src tests # check only duplicate functions -vendor/bin/detect-collisions --constants src tests # check only duplicate constants -``` - Example output: ``` Foo\NamespacedClass2 is defined 2 times: > /tests/sample-collisions/file2.php > /tests/sample-collisions/file2.php -GlobalClass1 is defined 2 times: +GlobalInterface1 is defined 2 times: > /tests/sample-collisions/file1.php > /tests/sample-collisions/file2.php ``` @@ -41,5 +33,10 @@ And in such cases, the test may work when executed in standalone run, but fail w Therefore, having a collision detector in CI might be useful. ## Versions -- 1.x supports PHP 7.2 - PHP 8.2 +- 1.x + - PHP 7.2 - PHP 8.2 + - is very slow, but supports finding function & constant duplicates +- 2.x +- - PHP 7.2 - PHP 8.2 + - fast, support finding only class duplicates diff --git a/bin/detect-collisions b/bin/detect-collisions index 439b123..52705cf 100755 --- a/bin/detect-collisions +++ b/bin/detect-collisions @@ -1,6 +1,7 @@ #!/usr/bin/env php getCollidingClasses(); + +} catch (InvalidPathProvidedException | FileParsingException $e) { echo "ERROR: {$e->getMessage()}\n"; exit(255); } -echo "Checking duplicates of " . implode(' and ', $check) . " in " . implode(', ', $providedFolders) . ":\n\n"; - -$collisions = array_merge( - in_array('classes', $check, true) ? $detector->getCollidingClasses() : [], - in_array('functions', $check, true) ? $detector->getCollidingFunctions() : [], - in_array('constants', $check, true) ? $detector->getCollidingConstants() : [] -); - foreach ($collisions as $name => $filePaths) { $count = count($filePaths); echo "$name is defined $count times:\n"; diff --git a/composer.json b/composer.json index 44bb309..27f3cb5 100644 --- a/composer.json +++ b/composer.json @@ -1,17 +1,19 @@ { "name": "shipmonk/name-collision-detector", - "description": "Simple tool to find class/function/constant name duplicates within your project.", + "description": "Simple tool to find ambiguous classes (class name duplicates) within your project.", "license": [ "MIT" ], "keywords": [ "namespace", "collision", + "ambiguous", "autoload", "classname" ], "require": { - "roave/better-reflection": "4.* || 5.* || 6.*" + "php": "^7.2 || ^8.0", + "ext-tokenizer": "*" }, "require-dev": { "editorconfig-checker/editorconfig-checker": "^10.3.0", diff --git a/composer.lock b/composer.lock index dee6fd7..094aaa4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,227 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2902b0809a92f3da3bcc0c94d2ef70a3", - "packages": [ - { - "name": "jetbrains/phpstorm-stubs", - "version": "v2022.3", - "source": { - "type": "git", - "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "6b568c153cea002dc6fad96285c3063d07cab18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/6b568c153cea002dc6fad96285c3063d07cab18d", - "reference": "6b568c153cea002dc6fad96285c3063d07cab18d", - "shasum": "" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "@stable", - "nikic/php-parser": "@stable", - "php": "^8.0", - "phpdocumentor/reflection-docblock": "@stable", - "phpunit/phpunit": "@stable" - }, - "type": "library", - "autoload": { - "files": [ - "PhpStormStubsMap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "PHP runtime & extensions header files for PhpStorm", - "homepage": "https://www.jetbrains.com/phpstorm", - "keywords": [ - "autocomplete", - "code", - "inference", - "inspection", - "jetbrains", - "phpstorm", - "stubs", - "type" - ], - "support": { - "source": "https://github.com/JetBrains/phpstorm-stubs/tree/v2022.3" - }, - "time": "2022-10-17T09:21:37+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.15.4", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" - }, - "time": "2023-03-05T19:49:14+00:00" - }, - { - "name": "roave/better-reflection", - "version": "6.8.0", - "source": { - "type": "git", - "url": "https://github.com/Roave/BetterReflection.git", - "reference": "cc6e8c4606fb52d3bc2b56dbb6dda72e840bb8b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/BetterReflection/zipball/cc6e8c4606fb52d3bc2b56dbb6dda72e840bb8b0", - "reference": "cc6e8c4606fb52d3bc2b56dbb6dda72e840bb8b0", - "shasum": "" - }, - "require": { - "ext-json": "*", - "jetbrains/phpstorm-stubs": "2022.3", - "nikic/php-parser": "^4.15.4", - "php": "~8.1.0 || ~8.2.0", - "roave/signature": "^1.7" - }, - "conflict": { - "thecodingmachine/safe": "<1.1.3" - }, - "require-dev": { - "doctrine/coding-standard": "^11.1.0", - "phpstan/phpstan": "^1.10.3", - "phpstan/phpstan-phpunit": "^1.3.10", - "phpunit/phpunit": "10.0.14", - "roave/infection-static-analysis-plugin": "^1.29.0", - "vimeo/psalm": "5.7.7" - }, - "suggest": { - "composer/composer": "Required to use the ComposerSourceLocator" - }, - "type": "library", - "autoload": { - "psr-4": { - "Roave\\BetterReflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "James Titcumb", - "email": "james@asgrim.com", - "homepage": "https://github.com/asgrim" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - }, - { - "name": "Gary Hockin", - "email": "gary@roave.com", - "homepage": "https://github.com/geeh" - }, - { - "name": "Jaroslav HanslĂ­k", - "email": "kukulich@kukulich.cz", - "homepage": "https://github.com/kukulich" - } - ], - "description": "Better Reflection - an improved code reflection API", - "support": { - "issues": "https://github.com/Roave/BetterReflection/issues", - "source": "https://github.com/Roave/BetterReflection/tree/6.8.0" - }, - "time": "2023-03-06T09:15:25+00:00" - }, - { - "name": "roave/signature", - "version": "1.7.0", - "source": { - "type": "git", - "url": "https://github.com/Roave/Signature.git", - "reference": "2ab4eadcb9f9d449f673a97b67797403b35eca94" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/Signature/zipball/2ab4eadcb9f9d449f673a97b67797403b35eca94", - "reference": "2ab4eadcb9f9d449f673a97b67797403b35eca94", - "shasum": "" - }, - "require": { - "php": "8.0.*|8.1.*|8.2.*" - }, - "require-dev": { - "doctrine/coding-standard": "^10.0.0", - "infection/infection": "^0.26.15", - "phpunit/phpunit": "^9.5.25", - "vimeo/psalm": "^4.28.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Roave\\Signature\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Sign and verify stuff", - "support": { - "issues": "https://github.com/Roave/Signature/issues", - "source": "https://github.com/Roave/Signature/tree/1.7.0" - }, - "time": "2022-10-10T08:44:53+00:00" - } - ], + "content-hash": "e5539aee63f4a92149b1491dd99a23f2", + "packages": [], "packages-dev": [ { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -1005,6 +786,62 @@ ], "time": "2023-03-08T13:26:56+00:00" }, + { + "name": "nikic/php-parser", + "version": "v4.15.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + }, + "time": "2023-03-05T19:49:14+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.3", @@ -1118,16 +955,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.18.1", + "version": "1.20.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f" + "reference": "90490bd8fd8530a272043c4950c180b6d0cf5f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/22dcdfd725ddf99583bfe398fc624ad6c5004a0f", - "reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/90490bd8fd8530a272043c4950c180b6d0cf5f81", + "reference": "90490bd8fd8530a272043c4950c180b6d0cf5f81", "shasum": "" }, "require": { @@ -1157,22 +994,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.18.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.2" }, - "time": "2023-04-07T11:51:11+00:00" + "time": "2023-04-22T12:59:35+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.13", + "version": "1.10.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70" + "reference": "d232901b09e67538e5c86a724be841bea5768a7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f07bf8c6980b81bf9e49d44bd0caf2e737614a70", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", + "reference": "d232901b09e67538e5c86a724be841bea5768a7c", "shasum": "" }, "require": { @@ -1221,7 +1058,7 @@ "type": "tidelift" } ], - "time": "2023-04-12T19:29:52+00:00" + "time": "2023-04-19T13:47:27+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1644,16 +1481,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.6", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -1727,7 +1564,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -1743,7 +1580,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T11:43:46+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "sebastian/cli-parser", @@ -2711,32 +2548,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.10.0", + "version": "8.11.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "c4e213e6e57f741451a08e68ef838802eec92287" + "reference": "af87461316b257e46e15bb041dca6fca3796d822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/c4e213e6e57f741451a08e68ef838802eec92287", - "reference": "c4e213e6e57f741451a08e68ef838802eec92287", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/af87461316b257e46e15bb041dca6fca3796d822", + "reference": "af87461316b257e46e15bb041dca6fca3796d822", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": ">=1.18.0 <1.19.0", + "phpstan/phpdoc-parser": ">=1.20.0 <1.21.0", "squizlabs/php_codesniffer": "^3.7.1" }, "require-dev": { "phing/phing": "2.17.4", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.10.11", + "phpstan/phpstan": "1.10.14", "phpstan/phpstan-deprecation-rules": "1.1.3", - "phpstan/phpstan-phpunit": "1.0.0|1.3.11", + "phpstan/phpstan-phpunit": "1.3.11", "phpstan/phpstan-strict-rules": "1.5.1", - "phpunit/phpunit": "7.5.20|8.5.21|9.6.6|10.0.19" + "phpunit/phpunit": "7.5.20|8.5.21|9.6.6|10.1.1" }, "type": "phpcodesniffer-standard", "extra": { @@ -2760,7 +2597,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.10.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.11.1" }, "funding": [ { @@ -2772,7 +2609,7 @@ "type": "tidelift" } ], - "time": "2023-04-10T07:39:29+00:00" + "time": "2023-04-24T08:19:01+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -2887,7 +2724,10 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "php": "^7.2 || ^8.0", + "ext-tokenizer": "*" + }, "platform-dev": [], "plugin-api-version": "2.3.0" } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 1e3d28d..5101be6 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -18,7 +18,7 @@ src/ tests/ - tests/sample-collisions/* + tests/data/* @@ -344,7 +344,6 @@ - @@ -368,8 +367,7 @@ @@ -378,8 +376,7 @@ @@ -387,8 +384,7 @@ diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d15aabf..b100e15 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,22 +11,8 @@ parameters: - tests excludePaths: analyseAndScan: - - tests/sample-collisions/* + - tests/data/* tmpDir: cache/phpstan/ checkMissingCallableSignature: true checkUninitializedProperties: true checkTooWideReturnTypesInProtectedAndPublicMethods: true - ignoreErrors: - # we want to support even < PHP 8.0 which requires better-reflection 4.* using different reflectors - - - message: '#Roave\\BetterReflection\\Reflector\\DefaultReflector#' - reportUnmatched: false - - - message: '#Roave\\BetterReflection\\Reflector\\ClassReflector#' - reportUnmatched: false - - - message: '#Roave\\BetterReflection\\Reflector\\FunctionReflector#' - reportUnmatched: false - - - message: '#Roave\\BetterReflection\\Reflector\\ConstantReflector#' - reportUnmatched: false diff --git a/src/FileParsingException.php b/src/FileParsingException.php new file mode 100644 index 0000000..2f558f9 --- /dev/null +++ b/src/FileParsingException.php @@ -0,0 +1,16 @@ +getMessage(), 0, $previous); + parent::__construct($reason); } } diff --git a/src/NameCollisionDetector.php b/src/NameCollisionDetector.php index 3b0f9d3..30e92ed 100644 --- a/src/NameCollisionDetector.php +++ b/src/NameCollisionDetector.php @@ -2,35 +2,45 @@ namespace ShipMonk; +use DirectoryIterator; +use Generator; use LogicException; -use Roave\BetterReflection\BetterReflection; -use Roave\BetterReflection\Reflection\ReflectionClass; -use Roave\BetterReflection\Reflection\ReflectionConstant; -use Roave\BetterReflection\Reflection\ReflectionFunction; -use Roave\BetterReflection\Reflector\ClassReflector; -use Roave\BetterReflection\Reflector\ConstantReflector; -use Roave\BetterReflection\Reflector\DefaultReflector; -use Roave\BetterReflection\Reflector\FunctionReflector; -use Roave\BetterReflection\SourceLocator\Exception\InvalidDirectory; -use Roave\BetterReflection\SourceLocator\Exception\InvalidFileInfo; -use Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator; -use Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator; -use Roave\BetterReflection\SourceLocator\Type\DirectoriesSourceLocator; -use Roave\BetterReflection\SourceLocator\Type\SourceLocator; -use function class_exists; +use ParseError; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; use function count; +use function file_get_contents; +use function is_array; +use function is_dir; use function ksort; use function preg_quote; use function preg_replace; use function sort; +use function substr; +use function token_get_all; +use const PHP_VERSION_ID; +use const T_CLASS; +use const T_COMMENT; +use const T_CURLY_OPEN; +use const T_DOC_COMMENT; +use const T_DOLLAR_OPEN_CURLY_BRACES; +use const T_ENUM; +use const T_INTERFACE; +use const T_NAME_QUALIFIED; +use const T_NAMESPACE; +use const T_NS_SEPARATOR; +use const T_STRING; +use const T_TRAIT; +use const T_WHITESPACE; +use const TOKEN_PARSE; class NameCollisionDetector { /** - * @var SourceLocator + * @var list */ - private $sourceLocator; + private $directories; /** * @var string|null @@ -38,101 +48,35 @@ class NameCollisionDetector private $cwd; /** - * Based on: https://github.com/Roave/BetterReflection/blob/396a07c9d276cb9ffba581b24b2dadbb542d542e/demo/parsing-whole-directory/example2.php - * * @param list $directories * @param string|null $cwd Path prefix to strip * @throws InvalidPathProvidedException */ public function __construct(array $directories, ?string $cwd = null) { - try { - $astLocator = (new BetterReflection())->astLocator(); - $sourceLocator = new AggregateSourceLocator([ - new DirectoriesSourceLocator( - $directories, - $astLocator - ), - new AutoloadSourceLocator($astLocator) - ]); - } catch (InvalidFileInfo | InvalidDirectory $e) { - throw new InvalidPathProvidedException($e); + foreach ($directories as $directory) { + if (!is_dir($directory)) { + throw new InvalidPathProvidedException("Path \"$directory\" is not directory"); + } } - $this->sourceLocator = $sourceLocator; + $this->directories = $directories; $this->cwd = $cwd; } - /** - * @return array> - */ - public function getCollidingConstants(): array - { - return $this->getCollisions($this->reflectAllConstants()); - } - - /** - * @return array> - */ - public function getCollidingFunctions(): array - { - return $this->getCollisions($this->reflectAllFunctions()); - } - /** * @return array> */ public function getCollidingClasses(): array - { - return $this->getCollisions($this->reflectAllClasses()); - } - - /** - * @return iterable - */ - private function reflectAllClasses(): iterable - { - return class_exists(ClassReflector::class) - ? (new ClassReflector($this->sourceLocator))->getAllClasses() - : (new DefaultReflector($this->sourceLocator))->reflectAllClasses(); - } - - /** - * @return iterable - */ - private function reflectAllFunctions(): iterable - { - return class_exists(FunctionReflector::class) - ? (new FunctionReflector($this->sourceLocator, new ClassReflector($this->sourceLocator)))->getAllFunctions() - : (new DefaultReflector($this->sourceLocator))->reflectAllFunctions(); - } - - /** - * @return iterable - */ - private function reflectAllConstants(): iterable - { - return class_exists(ConstantReflector::class) - ? (new ConstantReflector($this->sourceLocator, new ClassReflector($this->sourceLocator)))->getAllConstants() - : (new DefaultReflector($this->sourceLocator))->reflectAllConstants(); - } - - /** - * @param iterable|iterable|iterable $reflections - * @return array> - */ - private function getCollisions(iterable $reflections): array { $classToFilesMap = []; - foreach ($reflections as $reflection) { - $className = $reflection->getName(); - - if (!isset($classToFilesMap[$className])) { - $classToFilesMap[$className] = []; + foreach ($this->directories as $directory) { + foreach ($this->listPhpFilesIn($directory) as $filePath) { + foreach ($this->getClassesInFile($filePath) as $class) { + $classToFilesMap[$class][] = $this->normalizePath($filePath); + } } - - $classToFilesMap[$className][] = $this->normalizeFileName($reflection->getFileName()); } ksort($classToFilesMap); @@ -149,15 +93,11 @@ private function getCollisions(iterable $reflections): array return $classToFilesMap; } - private function normalizeFileName(?string $fileName): string + private function normalizePath(string $path): string { - if ($fileName === null) { - return 'unknown file'; - } - if ($this->cwd !== null) { $cwdForRegEx = preg_quote($this->cwd, '~'); - $replacedFileName = preg_replace("~^{$cwdForRegEx}~", '', $fileName); + $replacedFileName = preg_replace("~^{$cwdForRegEx}~", '', $path); if ($replacedFileName === null) { throw new LogicException('Invalid regex, should not happen'); @@ -166,7 +106,101 @@ private function normalizeFileName(?string $fileName): string return $replacedFileName; } - return $fileName; + return $path; + } + + /** + * Searches classes, interfaces and traits in PHP file. + * Based on Nette\Loaders\RobotLoader::scanPhp + * + * @return list + */ + private function getClassesInFile(string $file): array + { + $code = file_get_contents($file); + + if ($code === false) { + throw new FileParsingException("Unable to get contents of $file"); + } + + $expected = false; + $namespace = $name = ''; + $level = $minLevel = 0; + $classes = []; + + try { + $tokens = token_get_all($code, TOKEN_PARSE); + } catch (ParseError $e) { + throw new FileParsingException("Unable to parse $file: " . $e->getMessage(), $e); + } + + foreach ($tokens as $token) { + if (is_array($token)) { + switch ($token[0]) { + case T_COMMENT: + case T_DOC_COMMENT: + case T_WHITESPACE: + continue 2; + + case T_STRING: + case PHP_VERSION_ID < 80000 ? T_NS_SEPARATOR : T_NAME_QUALIFIED: + if ($expected !== null && $expected !== false) { + $name .= $token[1]; + } + + continue 2; + + case T_NAMESPACE: + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + case PHP_VERSION_ID < 80100 ? T_CLASS : T_ENUM: + $expected = $token[0]; + $name = ''; + continue 2; + + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + $level++; + } + } + + if ($expected !== null && $expected !== false) { + if ($expected === T_NAMESPACE) { + $namespace = $name !== '' ? $name . '\\' : ''; + $minLevel = $token === '{' ? 1 : 0; + } elseif ($name !== '' && $level === $minLevel) { + $classes[] = $namespace . $name; + } + + $expected = null; + } + + if ($token === '{') { + $level++; + } elseif ($token === '}') { + $level--; + } + } + + return $classes; + } + + /** + * @return Generator + */ + private function listPhpFilesIn(string $directory): Generator + { + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)); + + foreach ($iterator as $entry) { + /** @var DirectoryIterator $entry */ + if (!$entry->isFile() || !$entry->isReadable() || substr($entry->getFilename(), -4) !== '.php') { + continue; + } + + yield $entry->getPathname(); + } } } diff --git a/tests/NameCollisionDetectorTest.php b/tests/NameCollisionDetectorTest.php index 69ea5a5..4fdcc9b 100644 --- a/tests/NameCollisionDetectorTest.php +++ b/tests/NameCollisionDetectorTest.php @@ -15,97 +15,70 @@ class NameCollisionDetectorTest extends TestCase public function testBinScript(): void { $expectedNoDirectory = "ERROR: no directories provided, use e.g. `detect-collisions src tests`\n"; - $expectedInvalidDirectoryRegex = "~^ERROR: \".*?/tests/nonsense\" does not exist\n$~"; - $expectedSuccessRegex = <<<'EOF' -~Checking duplicates of classes and functions and constants in ../src: + $expectedInvalidDirectoryRegex = "~^ERROR: Path \".*?/tests/nonsense\" is not directory\n$~"; + $parsingFailed = "~^ERROR: Unable to parse .*?/tests/data/parse-failure/file1.php: .*?\n$~"; + $expectedSuccessRegex = '~OK: no name collision found in: .*?/src~'; -OK: no name collision found in: .*?/src~ -EOF; $space = ' '; // bypass editorconfig checker $expectedClasses = << /sample-collisions/file1.php -$space> /sample-collisions/file2.php +$space> /data/sample-collisions/file1.php +$space> /data/sample-collisions/file2.php Foo\NamespacedClass2 is defined 2 times: -$space> /sample-collisions/file2.php -$space> /sample-collisions/file2.php +$space> /data/sample-collisions/file2.php +$space> /data/sample-collisions/file2.php GlobalClass1 is defined 2 times: -$space> /sample-collisions/file1.php -$space> /sample-collisions/file2.php +$space> /data/sample-collisions/file1.php +$space> /data/sample-collisions/file2.php GlobalClass2 is defined 2 times: -$space> /sample-collisions/file2.php -$space> /sample-collisions/file2.php +$space> /data/sample-collisions/file2.php +$space> /data/sample-collisions/file2.php EOF; - self::assertSame($expectedClasses, $this->runCommand(__DIR__ . '/../bin/detect-collisions --classes sample-collisions', 1)); - self::assertSame($expectedNoDirectory, $this->runCommand(__DIR__ . '/../bin/detect-collisions', 255)); - self::assertSame(1, preg_match($expectedInvalidDirectoryRegex, $this->runCommand(__DIR__ . '/../bin/detect-collisions nonsense', 255))); - self::assertSame(1, preg_match($expectedSuccessRegex, $this->runCommand(__DIR__ . '/../bin/detect-collisions ../src', 0))); + $regularOutput = $this->runCommand(__DIR__ . '/../bin/detect-collisions data/sample-collisions', 1); + $parseFailedOutput = $this->runCommand(__DIR__ . '/../bin/detect-collisions data/parse-failure', 255); + $noDirectoryOutput = $this->runCommand(__DIR__ . '/../bin/detect-collisions', 255); + $invalidDirectoryOutput = $this->runCommand(__DIR__ . '/../bin/detect-collisions nonsense', 255); + $successOutput = $this->runCommand(__DIR__ . '/../bin/detect-collisions ../src', 0); + + self::assertSame($expectedClasses, $regularOutput); + self::assertSame($expectedNoDirectory, $noDirectoryOutput); + self::assertSame(1, preg_match($parsingFailed, $parseFailedOutput)); + self::assertSame(1, preg_match($expectedSuccessRegex, $successOutput)); + self::assertSame(1, preg_match($expectedInvalidDirectoryRegex, $invalidDirectoryOutput)); } public function testCollisionDetection(): void { - $detector = new NameCollisionDetector([__DIR__ . '/sample-collisions'], __DIR__); + $detector = new NameCollisionDetector([__DIR__ . '/data/sample-collisions'], __DIR__); $collidingClasses = $detector->getCollidingClasses(); - $collidingFunctions = $detector->getCollidingFunctions(); - $collidingConstants = $detector->getCollidingConstants(); self::assertSame( [ 'Foo\NamespacedClass1' => [ - '/sample-collisions/file1.php', - '/sample-collisions/file2.php', + '/data/sample-collisions/file1.php', + '/data/sample-collisions/file2.php', ], 'Foo\NamespacedClass2' => [ - '/sample-collisions/file2.php', - '/sample-collisions/file2.php', + '/data/sample-collisions/file2.php', + '/data/sample-collisions/file2.php', ], 'GlobalClass1' => [ - '/sample-collisions/file1.php', - '/sample-collisions/file2.php', + '/data/sample-collisions/file1.php', + '/data/sample-collisions/file2.php', ], 'GlobalClass2' => [ - '/sample-collisions/file2.php', - '/sample-collisions/file2.php', + '/data/sample-collisions/file2.php', + '/data/sample-collisions/file2.php', ], ], $collidingClasses ); - - self::assertSame( - [ - 'Foo\namespacedFunction' => [ - '/sample-collisions/file2.php', - '/sample-collisions/file2.php', - ], - 'globalFunction' => [ - '/sample-collisions/file1.php', - '/sample-collisions/file2.php', - ], - ], - $collidingFunctions - ); - - self::assertSame( - [ - 'Foo\NAMESPACED_CONST' => [ - '/sample-collisions/file1.php', - '/sample-collisions/file2.php', - ], - 'GLOBAL_CONST' => [ - '/sample-collisions/file1.php', - '/sample-collisions/file2.php', - ], - ], - $collidingConstants - ); } private function runCommand(string $command, int $expectedExitCode): string diff --git a/tests/data/parse-failure/file1.php b/tests/data/parse-failure/file1.php new file mode 100644 index 0000000..a74771c --- /dev/null +++ b/tests/data/parse-failure/file1.php @@ -0,0 +1,3 @@ + Date: Mon, 24 Apr 2023 17:55:38 +0200 Subject: [PATCH 2/5] licence --- phpcs.xml.dist | 2 +- src/NameCollisionDetector.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 5101be6..51da0b4 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -291,7 +291,7 @@ - + diff --git a/src/NameCollisionDetector.php b/src/NameCollisionDetector.php index 30e92ed..2765f24 100644 --- a/src/NameCollisionDetector.php +++ b/src/NameCollisionDetector.php @@ -113,6 +113,7 @@ private function normalizePath(string $path): string * Searches classes, interfaces and traits in PHP file. * Based on Nette\Loaders\RobotLoader::scanPhp * + * @license https://github.com/nette/robot-loader/blob/v3.4.0/license.md * @return list */ private function getClassesInFile(string $file): array From de25dc5aa27494d8da37c046f28fd7c1fc7d9d5b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 25 Apr 2023 10:43:45 +0200 Subject: [PATCH 3/5] Add support for consts and functions --- README.md | 14 ++++------ bin/detect-collisions | 2 +- composer.json | 5 ++-- src/NameCollisionDetector.php | 25 +++++++++++------- tests/NameCollisionDetectorTest.php | 36 ++++++++++++++++++++++++-- tests/data/sample-collisions/file2.php | 4 +-- tests/data/sample-collisions/file3.php | 13 ++++++++++ tests/data/sample-collisions/file4.php | 6 +++++ 8 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 tests/data/sample-collisions/file3.php create mode 100644 tests/data/sample-collisions/file4.php diff --git a/README.md b/README.md index 1475f54..6ea4bc1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Name collision detector -Simple tool which allows you to detect if there are no classes (or interfaces or traits) defined multiple times within the same namespace. +Simple tool which allows you to detect if there are no types defined multiple times within the same namespace. +This means that any ambiguous class, interface, enum, trait, constant or function is reported. Non-zero exit code is returned when any duplicate is found. ## Installation: @@ -10,7 +11,7 @@ composer require --dev shipmonk/name-collision-detector ``` ## Usage: -Check duplicate classes: +Check duplicate types: ```sh vendor/bin/detect-collisions dir1 dir2 dir3 ``` @@ -32,11 +33,6 @@ Typically, you have PSR-4 autoloading solving this problem for you, but there ar And in such cases, the test may work when executed in standalone run, but fail when running all the tests together (depending on which class was autoloaded first). Therefore, having a collision detector in CI might be useful. -## Versions -- 1.x - - PHP 7.2 - PHP 8.2 - - is very slow, but supports finding function & constant duplicates -- 2.x -- - PHP 7.2 - PHP 8.2 - - fast, support finding only class duplicates +## Supported PHP versions +- PHP 7.2 - PHP 8.2 diff --git a/bin/detect-collisions b/bin/detect-collisions index 52705cf..0ff766e 100755 --- a/bin/detect-collisions +++ b/bin/detect-collisions @@ -29,7 +29,7 @@ if ($directories === []) { try { $detector = new NameCollisionDetector($directories, $cwd); - $collisions = $detector->getCollidingClasses(); + $collisions = $detector->getCollidingTypes(); } catch (InvalidPathProvidedException | FileParsingException $e) { echo "ERROR: {$e->getMessage()}\n"; diff --git a/composer.json b/composer.json index 27f3cb5..3bbef3c 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "shipmonk/name-collision-detector", - "description": "Simple tool to find ambiguous classes (class name duplicates) within your project.", + "description": "Simple tool to find ambiguous classes or any other name duplicates within your project.", "license": [ "MIT" ], @@ -9,7 +9,8 @@ "collision", "ambiguous", "autoload", - "classname" + "classname", + "autoloading" ], "require": { "php": "^7.2 || ^8.0", diff --git a/src/NameCollisionDetector.php b/src/NameCollisionDetector.php index 2765f24..dc53369 100644 --- a/src/NameCollisionDetector.php +++ b/src/NameCollisionDetector.php @@ -21,10 +21,12 @@ use const PHP_VERSION_ID; use const T_CLASS; use const T_COMMENT; +use const T_CONST; use const T_CURLY_OPEN; use const T_DOC_COMMENT; use const T_DOLLAR_OPEN_CURLY_BRACES; use const T_ENUM; +use const T_FUNCTION; use const T_INTERFACE; use const T_NAME_QUALIFIED; use const T_NAMESPACE; @@ -67,13 +69,13 @@ public function __construct(array $directories, ?string $cwd = null) /** * @return array> */ - public function getCollidingClasses(): array + public function getCollidingTypes(): array { $classToFilesMap = []; foreach ($this->directories as $directory) { foreach ($this->listPhpFilesIn($directory) as $filePath) { - foreach ($this->getClassesInFile($filePath) as $class) { + foreach ($this->getTypesInFile($filePath) as $class) { $classToFilesMap[$class][] = $this->normalizePath($filePath); } } @@ -110,13 +112,13 @@ private function normalizePath(string $path): string } /** - * Searches classes, interfaces and traits in PHP file. + * Searches enums, classes, interfaces, constants, functions and traits in PHP file. * Based on Nette\Loaders\RobotLoader::scanPhp * * @license https://github.com/nette/robot-loader/blob/v3.4.0/license.md * @return list */ - private function getClassesInFile(string $file): array + public function getTypesInFile(string $file): array { $code = file_get_contents($file); @@ -124,10 +126,10 @@ private function getClassesInFile(string $file): array throw new FileParsingException("Unable to get contents of $file"); } - $expected = false; + $expected = null; $namespace = $name = ''; $level = $minLevel = 0; - $classes = []; + $types = []; try { $tokens = token_get_all($code, TOKEN_PARSE); @@ -145,12 +147,14 @@ private function getClassesInFile(string $file): array case T_STRING: case PHP_VERSION_ID < 80000 ? T_NS_SEPARATOR : T_NAME_QUALIFIED: - if ($expected !== null && $expected !== false) { + if ($expected !== null) { $name .= $token[1]; } continue 2; + case T_CONST: + case T_FUNCTION: case T_NAMESPACE: case T_CLASS: case T_INTERFACE: @@ -166,12 +170,13 @@ private function getClassesInFile(string $file): array } } - if ($expected !== null && $expected !== false) { + if ($expected !== null) { if ($expected === T_NAMESPACE) { $namespace = $name !== '' ? $name . '\\' : ''; $minLevel = $token === '{' ? 1 : 0; + } elseif ($name !== '' && $level === $minLevel) { - $classes[] = $namespace . $name; + $types[] = $namespace . $name; } $expected = null; @@ -184,7 +189,7 @@ private function getClassesInFile(string $file): array } } - return $classes; + return $types; } /** diff --git a/tests/NameCollisionDetectorTest.php b/tests/NameCollisionDetectorTest.php index 4fdcc9b..1566d08 100644 --- a/tests/NameCollisionDetectorTest.php +++ b/tests/NameCollisionDetectorTest.php @@ -21,6 +21,10 @@ public function testBinScript(): void $space = ' '; // bypass editorconfig checker $expectedClasses = << /data/sample-collisions/file1.php +$space> /data/sample-collisions/file2.php + Foo\NamespacedClass1 is defined 2 times: $space> /data/sample-collisions/file1.php $space> /data/sample-collisions/file2.php @@ -29,6 +33,14 @@ public function testBinScript(): void $space> /data/sample-collisions/file2.php $space> /data/sample-collisions/file2.php +Foo\\namespacedFunction is defined 2 times: +$space> /data/sample-collisions/file2.php +$space> /data/sample-collisions/file2.php + +GLOBAL_CONST is defined 2 times: +$space> /data/sample-collisions/file1.php +$space> /data/sample-collisions/file2.php + GlobalClass1 is defined 2 times: $space> /data/sample-collisions/file1.php $space> /data/sample-collisions/file2.php @@ -37,6 +49,10 @@ public function testBinScript(): void $space> /data/sample-collisions/file2.php $space> /data/sample-collisions/file2.php +globalFunction is defined 2 times: +$space> /data/sample-collisions/file1.php +$space> /data/sample-collisions/file2.php + EOF; @@ -56,18 +72,30 @@ public function testBinScript(): void public function testCollisionDetection(): void { $detector = new NameCollisionDetector([__DIR__ . '/data/sample-collisions'], __DIR__); - $collidingClasses = $detector->getCollidingClasses(); + $collidingClasses = $detector->getCollidingTypes(); self::assertSame( [ - 'Foo\NamespacedClass1' => [ + 'Foo\NAMESPACED_CONST' => [ '/data/sample-collisions/file1.php', '/data/sample-collisions/file2.php', ], + 'Foo\NamespacedClass1' => [ + '/data/sample-collisions/file1.php', + '/data/sample-collisions/file2.php', + ], 'Foo\NamespacedClass2' => [ '/data/sample-collisions/file2.php', '/data/sample-collisions/file2.php', ], + 'Foo\namespacedFunction' => [ + '/data/sample-collisions/file2.php', + '/data/sample-collisions/file2.php', + ], + 'GLOBAL_CONST' => [ + '/data/sample-collisions/file1.php', + '/data/sample-collisions/file2.php', + ], 'GlobalClass1' => [ '/data/sample-collisions/file1.php', '/data/sample-collisions/file2.php', @@ -76,6 +104,10 @@ public function testCollisionDetection(): void '/data/sample-collisions/file2.php', '/data/sample-collisions/file2.php', ], + 'globalFunction' => [ + '/data/sample-collisions/file1.php', + '/data/sample-collisions/file2.php', + ], ], $collidingClasses ); diff --git a/tests/data/sample-collisions/file2.php b/tests/data/sample-collisions/file2.php index 3c19313..1769221 100644 --- a/tests/data/sample-collisions/file2.php +++ b/tests/data/sample-collisions/file2.php @@ -25,7 +25,7 @@ class NamespacedClass1 { } - class NamespacedClass2 { + interface NamespacedClass2 { } @@ -37,7 +37,7 @@ function namespacedFunction() { } namespace Foo { - class NamespacedClass2 { + trait NamespacedClass2 { } diff --git a/tests/data/sample-collisions/file3.php b/tests/data/sample-collisions/file3.php new file mode 100644 index 0000000..d00b23c --- /dev/null +++ b/tests/data/sample-collisions/file3.php @@ -0,0 +1,13 @@ + Date: Tue, 25 Apr 2023 12:03:59 +0200 Subject: [PATCH 4/5] Better tests, fix use statement false positive --- src/NameCollisionDetector.php | 37 +++++++++++++++++++++++++- tests/data/sample-collisions/file2.php | 6 +++-- tests/data/sample-collisions/file3.php | 19 ++++++------- tests/data/sample-collisions/file4.php | 16 +++++++++++ tests/data/sample-collisions/file5.php | 11 ++++++++ tests/data/sample-collisions/file6.php | 8 ++++++ tests/data/sample-collisions/file7.php | 11 ++++++++ tests/data/sample-collisions/file8.php | 3 +++ 8 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 tests/data/sample-collisions/file5.php create mode 100644 tests/data/sample-collisions/file6.php create mode 100644 tests/data/sample-collisions/file7.php create mode 100644 tests/data/sample-collisions/file8.php diff --git a/src/NameCollisionDetector.php b/src/NameCollisionDetector.php index dc53369..f3465be 100644 --- a/src/NameCollisionDetector.php +++ b/src/NameCollisionDetector.php @@ -10,6 +10,7 @@ use RecursiveIteratorIterator; use function count; use function file_get_contents; +use function in_array; use function is_array; use function is_dir; use function ksort; @@ -33,6 +34,7 @@ use const T_NS_SEPARATOR; use const T_STRING; use const T_TRAIT; +use const T_USE; use const T_WHITESPACE; use const TOKEN_PARSE; @@ -137,7 +139,7 @@ public function getTypesInFile(string $file): array throw new FileParsingException("Unable to parse $file: " . $e->getMessage(), $e); } - foreach ($tokens as $token) { + foreach ($tokens as $index => $token) { if (is_array($token)) { switch ($token[0]) { case T_COMMENT: @@ -160,6 +162,13 @@ public function getTypesInFile(string $file): array case T_INTERFACE: case T_TRAIT: case PHP_VERSION_ID < 80100 ? T_CLASS : T_ENUM: + if ( + ($token[0] === T_FUNCTION || $token[0] === T_CONST) + && $this->isWithinUseStatement($tokens, $index) + ) { + break; + } + $expected = $token[0]; $name = ''; continue 2; @@ -209,4 +218,30 @@ private function listPhpFilesIn(string $directory): Generator } } + /** + * Helps to prevent detecting use statements as function/const definitions + * - "use function fn" + * - "use const FOO" + * + * Use statement with braces "use Foo\{ function fn }" is filtered out by $level === $minLevel condition above + * + * @param mixed[] $tokens + */ + private function isWithinUseStatement(array $tokens, int $index): bool + { + do { + $previousToken = $tokens[--$index]; + + if (!is_array($previousToken)) { + return false; + } + + if ($previousToken[0] === T_USE) { + return true; + } + } while (in_array($previousToken[0], [T_COMMENT, T_DOC_COMMENT, T_WHITESPACE], true)); + + return false; + } + } diff --git a/tests/data/sample-collisions/file2.php b/tests/data/sample-collisions/file2.php index 1769221..46d6f23 100644 --- a/tests/data/sample-collisions/file2.php +++ b/tests/data/sample-collisions/file2.php @@ -25,7 +25,9 @@ class NamespacedClass1 { } - interface NamespacedClass2 { + interface + // comment + NamespacedClass2 { } @@ -37,7 +39,7 @@ function namespacedFunction() { } namespace Foo { - trait NamespacedClass2 { + trait/**/NamespacedClass2 { } diff --git a/tests/data/sample-collisions/file3.php b/tests/data/sample-collisions/file3.php index d00b23c..2bf0d8f 100644 --- a/tests/data/sample-collisions/file3.php +++ b/tests/data/sample-collisions/file3.php @@ -2,12 +2,13 @@ namespace Foo; -class NotCollidingAsScopedByClass { - const NAMESPACED_CONST = 1; - function namespacedFunction() {} -} - -new class { - const NAMESPACED_CONST = 1; - function namespacedFunction() {} -}; +use + function count; +use + Foo\{NamespacedClass1, NamespacedClass2, UniqueClass, UniqueInterface}; +use + function Foo\{namespacedFunction}; +use + const /* multiline */ + // single line + Foo\{NAMESPACED_CONST, UNIQUE_CONST}; diff --git a/tests/data/sample-collisions/file4.php b/tests/data/sample-collisions/file4.php index f39f835..b0b3bc9 100644 --- a/tests/data/sample-collisions/file4.php +++ b/tests/data/sample-collisions/file4.php @@ -1,6 +1,22 @@ + + From 9878312e812ecee37cde55edb79f8415f829ead6 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 25 Apr 2023 12:17:55 +0200 Subject: [PATCH 5/5] move some test code --- tests/data/sample-collisions/file4.php | 7 +++++++ tests/data/sample-collisions/file7.php | 16 +++++----------- tests/data/sample-collisions/file8.php | 3 --- 3 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 tests/data/sample-collisions/file8.php diff --git a/tests/data/sample-collisions/file4.php b/tests/data/sample-collisions/file4.php index b0b3bc9..2c14241 100644 --- a/tests/data/sample-collisions/file4.php +++ b/tests/data/sample-collisions/file4.php @@ -16,7 +16,14 @@ function globalFunction, }; class Foo { + const GLOBAL_CONST = 1; const NAMESPACED_CONST = 1; + function globalFunction() {} function namespacedFunction() {} } +new class { + const NAMESPACED_CONST = 1; + function namespacedFunction() {} +}; + diff --git a/tests/data/sample-collisions/file7.php b/tests/data/sample-collisions/file7.php index 7c01b47..2d06c7a 100644 --- a/tests/data/sample-collisions/file7.php +++ b/tests/data/sample-collisions/file7.php @@ -1,11 +1,5 @@ - + + diff --git a/tests/data/sample-collisions/file8.php b/tests/data/sample-collisions/file8.php deleted file mode 100644 index 3bcb0f3..0000000 --- a/tests/data/sample-collisions/file8.php +++ /dev/null @@ -1,3 +0,0 @@ - - -