From bc3061cc7da3515583d6d3aa66d8d84f1da717c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Mon, 16 Jul 2018 18:20:22 +0200 Subject: [PATCH] Reworking the whitelist API (#247) --- Makefile | 62 ++-- fixtures/set013/scoper.inc.php | 37 +- ...h-single-level-use-statement-and-alias.php | 32 +- ...-level-with-single-level-use-statement.php | 32 +- .../class-const/global-scope-single-level.php | 32 +- ...-level-with-single-level-use-and-alias.php | 38 +- ...-scope-two-level-with-single-level-use.php | 40 +- specs/class-const/global-scope-two-level.php | 32 +- ...h-single-level-use-statement-and-alias.php | 35 +- .../namespace-scope-single-level.php | 24 +- ...-level-with-single-level-use-and-alias.php | 35 +- ...-scope-two-level-with-single-level-use.php | 45 +-- .../class-const/namespace-scope-two-level.php | 54 +-- ...e-part-with-single-level-use-statement.php | 34 +- specs/class/abstract.php | 54 +-- specs/class/anonymous.php | 13 +- specs/class/conditional.php | 18 +- specs/class/final.php | 8 +- specs/class/interface.php | 8 +- specs/class/regular-extend.php | 10 +- specs/class/regular.php | 12 +- specs/class/trait.php | 8 +- specs/const/const-declaration.php | 12 +- ...th-single-level-use-statement-an-alias.php | 41 +-- ...e-part-with-single-level-use-statement.php | 24 +- specs/new/global-scope-single-part.php | 37 +- ...-parts-with-single-level-use-and-alias.php | 50 +-- ...-scope-two-parts-with-single-level-use.php | 68 +--- specs/new/global-scope-two-parts.php | 37 +- ...e-part-with-single-level-use-statement.php | 17 +- specs/new/namespace-single-part.php | 17 +- ...espace-two-parts-with-single-level-use.php | 15 +- ...namespace-two-parts-with-two-level-use.php | 40 +- specs/new/namespace-two-parts.php | 18 +- specs/string-literal/array-var.php | 18 +- specs/string-literal/const.php | 142 +++++++- .../{define.php => define-value.php} | 2 +- specs/string-literal/func-arg.php | 173 ++++++++- specs/string-literal/method-arg.php | 26 +- specs/string-literal/new.php | 185 ++++++++++ specs/string-literal/var.php | 28 +- specs/whitelist-case-sensitiveness.php | 4 +- src/Autoload/ScoperAutoloadGenerator.php | 33 +- src/PhpParser/Node/ClassAliasFuncCall.php | 48 +++ src/PhpParser/Node/PrefixedName.php | 44 +++ .../NodeVisitor/ClassAliasStmtAppender.php | 144 ++++++++ .../Collection/NamespaceStmtCollection.php | 6 +- .../Collection/UseStmtCollection.php | 51 ++- .../NodeVisitor/ConstStmtReplacer.php | 9 +- .../FunctionIdentifierRecorder.php | 28 +- .../NodeVisitor/NameStmtPrefixer.php | 38 +- .../NodeVisitor/NamespaceStmtPrefixer.php | 7 +- ...dParentNode.php => ParentNodeAppender.php} | 5 +- .../Resolver/FullyQualifiedNameResolver.php | 18 +- .../NodeVisitor/StringScalarPrefixer.php | 264 ++++++++------ .../NodeVisitor/UseStmt/UseStmtPrefixer.php | 8 +- .../NodeVisitor/WhitelistedClassAppender.php | 124 ------- src/PhpParser/TraverserFactory.php | 4 +- src/Scoper/Composer/AutoloadPrefixer.php | 2 +- src/Whitelist.php | 160 +++++--- tests/Scoper/PhpScoperTest.php | 2 + tests/WhitelistTest.php | 343 +++++++++++++++++- 62 files changed, 1659 insertions(+), 1296 deletions(-) rename specs/string-literal/{define.php => define-value.php} (93%) create mode 100644 specs/string-literal/new.php create mode 100644 src/PhpParser/Node/ClassAliasFuncCall.php create mode 100644 src/PhpParser/Node/PrefixedName.php create mode 100644 src/PhpParser/NodeVisitor/ClassAliasStmtAppender.php rename src/PhpParser/NodeVisitor/{AppendParentNode.php => ParentNodeAppender.php} (93%) delete mode 100644 src/PhpParser/NodeVisitor/WhitelistedClassAppender.php diff --git a/Makefile b/Makefile index ba16f12b..efcc744c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := help -PHPNOGC=php -d zend.enable_gc=0 +PHPBIN=php .PHONY: help help: @@ -35,7 +35,7 @@ test: tc e2e PHPUNIT=bin/phpunit tu: ## Run PHPUnit tests tu: bin/phpunit - $(PHPNOGC) $(PHPUNIT) + $(PHPBIN) $(PHPUNIT) .PHONY: tc tc: ## Run PHPUnit tests with test coverage @@ -55,7 +55,7 @@ PHPSCOPER=bin/php-scoper.phar .PHONY: e2e_004 e2e_004: ## Run end-to-end tests for the fixture set 004 — Source code case e2e_004: bin/php-scoper.phar - $(PHPNOGC) $(BOX) compile --working-dir fixtures/set004 + $(PHPBIN) $(BOX) compile --working-dir fixtures/set004 php build/set004/bin/greet.phar > build/set004/output diff fixtures/set004/expected-output build/set004/output @@ -63,7 +63,7 @@ e2e_004: bin/php-scoper.phar .PHONY: e2e_005 e2e_005: ## Run end-to-end tests for the fixture set 005 — Third-party code case e2e_005: bin/php-scoper.phar fixtures/set005/vendor - $(PHPNOGC) $(BOX) compile --working-dir fixtures/set005 + $(PHPBIN) $(BOX) compile --working-dir fixtures/set005 php build/set005/bin/greet.phar > build/set005/output diff fixtures/set005/expected-output build/set005/output @@ -71,7 +71,7 @@ e2e_005: bin/php-scoper.phar fixtures/set005/vendor .PHONY: e2e_011 e2e_011: ## Run end-to-end tests for the fixture set 011 — Whitelist case e2e_011: bin/php-scoper.phar fixtures/set011/vendor - $(PHPNOGC) $(BOX) compile --working-dir fixtures/set011 + $(PHPBIN) $(BOX) compile --working-dir fixtures/set011 cp -R fixtures/set011/tests/ build/set011/tests/ php build/set011/bin/greet.phar > build/set011/output @@ -88,7 +88,7 @@ e2e_013: bin/php-scoper.phar .PHONY: e2e_014 e2e_014: ## Run end-to-end tests for the fixture set 014 — Source code case with PSR-0 e2e_014: bin/php-scoper.phar - $(PHPNOGC) $(BOX) compile --working-dir fixtures/set014 + $(PHPBIN) $(BOX) compile --working-dir fixtures/set014 php build/set014/bin/greet.phar > build/set014/output diff fixtures/set014/expected-output build/set014/output @@ -96,7 +96,7 @@ e2e_014: bin/php-scoper.phar .PHONY: e2e_015 e2e_015: ## Run end-to-end tests for the fixture set 015 — Third-party code case with PSR-0 e2e_015: bin/php-scoper.phar fixtures/set015/vendor - $(PHPNOGC) $(BOX) compile --working-dir fixtures/set015 + $(PHPBIN) $(BOX) compile --working-dir fixtures/set015 php build/set015/bin/greet.phar > build/set015/output diff fixtures/set015/expected-output build/set015/output @@ -104,7 +104,7 @@ e2e_015: bin/php-scoper.phar fixtures/set015/vendor .PHONY: e2e_016 e2e_016: ## Run end-to-end tests for the fixture set 016 — Symfony Finder e2e_016: bin/php-scoper.phar fixtures/set016-symfony-finder/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix \ + $(PHPBIN) $(PHPSCOPER) add-prefix \ --working-dir=fixtures/set016-symfony-finder \ --output-dir=../../build/set016-symfony-finder \ --force \ @@ -119,7 +119,7 @@ e2e_016: bin/php-scoper.phar fixtures/set016-symfony-finder/vendor .PHONY: e2e_017 e2e_017: ## Run end-to-end tests for the fixture set 017 — Symfony DependencyInjection e2e_017: bin/php-scoper.phar fixtures/set017-symfony-di/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix \ + $(PHPBIN) $(PHPSCOPER) add-prefix \ --working-dir=fixtures/set017-symfony-di \ --output-dir=../../build/set017-symfony-di \ --force \ @@ -134,7 +134,7 @@ e2e_017: bin/php-scoper.phar fixtures/set017-symfony-di/vendor .PHONY: e2e_018 e2e_018: ## Run end-to-end tests for the fixture set 018 — Nikic PHP-Parser e2e_018: bin/php-scoper.phar fixtures/set018-nikic-parser/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix \ + $(PHPBIN) $(PHPSCOPER) add-prefix \ --working-dir=fixtures/set018-nikic-parser \ --prefix=_Prefixed \ --output-dir=../../build/set018-nikic-parser \ @@ -149,7 +149,7 @@ e2e_018: bin/php-scoper.phar fixtures/set018-nikic-parser/vendor .PHONY: e2e_019 e2e_019: ## Run end-to-end tests for the fixture set 019 — Symfony Console e2e_019: bin/php-scoper.phar fixtures/set019-symfony-console/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix --working-dir=fixtures/set019-symfony-console \ + $(PHPBIN) $(PHPSCOPER) add-prefix --working-dir=fixtures/set019-symfony-console \ --prefix=_Prefixed \ --output-dir=../../build/set019-symfony-console \ --force \ @@ -164,7 +164,7 @@ e2e_019: bin/php-scoper.phar fixtures/set019-symfony-console/vendor .PHONY: e2e_020 e2e_020: ## Run end-to-end tests for the fixture set 020 — Infection e2e_020: bin/php-scoper.phar fixtures/set020-infection/vendor clover.xml - $(PHPNOGC) $(PHPSCOPER) add-prefix --working-dir=fixtures/set020-infection \ + $(PHPBIN) $(PHPSCOPER) add-prefix --working-dir=fixtures/set020-infection \ --output-dir=../../build/set020-infection \ --force \ --no-interaction \ @@ -182,8 +182,8 @@ e2e_020: bin/php-scoper.phar fixtures/set020-infection/vendor clover.xml .PHONY: e2e_021 e2e_021: ## Run end-to-end tests for the fixture set 020 — Composer -e2e_021: bin/php-scoper.phar fixtures/set021-composer/vendor clover.xml - $(PHPNOGC) $(PHPSCOPER) add-prefix --working-dir=fixtures/set021-composer \ +e2e_021: bin/php-scoper.phar fixtures/set021-composer/vendor + $(PHPBIN) $(PHPSCOPER) add-prefix --working-dir=fixtures/set021-composer \ --output-dir=../../build/set021-composer \ --force \ --no-interaction \ @@ -203,7 +203,7 @@ e2e_021: bin/php-scoper.phar fixtures/set021-composer/vendor clover.xml .PHONY: e2e_022 e2e_022: ## Run end-to-end tests for the fixture set 022 — Whitelist the project code with namespace whitelisting e2e_022: bin/php-scoper.phar fixtures/set022/vendor - $(PHPNOGC) $(BOX) compile --working-dir fixtures/set022 + $(PHPBIN) $(BOX) compile --working-dir fixtures/set022 cp -R fixtures/set022/tests/ build/set022/tests/ php build/set022/bin/greet.phar > build/set022/output @@ -213,7 +213,7 @@ e2e_022: bin/php-scoper.phar fixtures/set022/vendor .PHONY: e2e_023 e2e_023: ## Run end-to-end tests for the fixture set 023 — Whitelisting a whole third-party component with namespace whitelisting e2e_023: bin/php-scoper.phar fixtures/set023/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix --working-dir=fixtures/set023 \ + $(PHPBIN) $(PHPSCOPER) add-prefix --working-dir=fixtures/set023 \ --output-dir=../../build/set023 \ --force \ --no-interaction \ @@ -226,7 +226,7 @@ e2e_023: bin/php-scoper.phar fixtures/set023/vendor .PHONY: e2e_024 e2e_024: ## Run end-to-end tests for the fixture set 024 — Whitelisting user functions registered in the global namespace e2e_024: bin/php-scoper.phar fixtures/set024/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix \ + $(PHPBIN) $(PHPSCOPER) add-prefix \ --working-dir=fixtures/set024 \ --output-dir=../../build/set024 \ --force \ @@ -241,7 +241,7 @@ e2e_024: bin/php-scoper.phar fixtures/set024/vendor .PHONY: e2e_025 e2e_025: ## Run end-to-end tests for the fixture set 025 — Whitelisting a vendor function e2e_025: bin/php-scoper.phar fixtures/set025/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix \ + $(PHPBIN) $(PHPSCOPER) add-prefix \ --working-dir=fixtures/set025 \ --output-dir=../../build/set025 \ --force \ @@ -255,7 +255,7 @@ e2e_025: bin/php-scoper.phar fixtures/set025/vendor .PHONY: e2e_026 e2e_026: ## Run end-to-end tests for the fixture set 026 — Whitelisting classes and functions with pattern matching e2e_026: bin/php-scoper.phar fixtures/set026/vendor - $(PHPNOGC) $(PHPSCOPER) add-prefix \ + $(PHPBIN) $(PHPSCOPER) add-prefix \ --working-dir=fixtures/set026 \ --output-dir=../../build/set026 \ --force \ @@ -270,32 +270,26 @@ e2e_026: bin/php-scoper.phar fixtures/set026/vendor .PHONY: tb BLACKFIRE=blackfire tb: ## Run Blackfire profiling -tb: vendor - rm -rf build - rm -rf vendor-bin/*/vendor - - mv -f vendor tmp-back - composer install --no-dev --prefer-dist --classmap-authoritative - - $(BLACKFIRE) --new-reference run $(PHPNOGC) bin/php-scoper add-prefix --output-dir=build/php-scoper --force --quiet - - rm -rf vendor - mv -f tmp-back vendor +tb: bin/php-scoper.phar vendor + $(BLACKFIRE) --new-reference run $(PHPBIN) bin/php-scoper.phar add-prefix --output-dir=build/php-scoper --force --quiet # # Rules from files #--------------------------------------------------------------------------- vendor: composer.lock - composer install + export COMPOSER_ROOT_VERSION='0.8.99'; composer install + unset "COMPOSER_ROOT_VERSION" touch $@ vendor/bamarni: composer.lock - composer install + export COMPOSER_ROOT_VERSION='0.8.99'; composer install + unset "COMPOSER_ROOT_VERSION" touch $@ bin/phpunit: composer.lock - composer install + export COMPOSER_ROOT_VERSION='0.8.99'; composer install + unset "COMPOSER_ROOT_VERSION" touch $@ vendor-bin/covers-validator/vendor: vendor-bin/covers-validator/composer.lock vendor/bamarni @@ -404,7 +398,7 @@ bin/php-scoper.phar: bin/php-scoper src vendor scoper.inc.php box.json box.json: box.json.dist cat box.json.dist | sed -E 's/\"key\": \".+\",//g' | sed -E 's/\"algorithm\": \".+\",//g' > box.json -COVERS_VALIDATOR=$(PHPNOGC) vendor-bin/covers-validator/bin/covers-validator +COVERS_VALIDATOR=$(PHPBIN) vendor-bin/covers-validator/bin/covers-validator clover.xml: src $(COVERS_VALIDATOR) phpdbg -qrr -d zend.enable_gc=0 $(PHPUNIT) \ diff --git a/fixtures/set013/scoper.inc.php b/fixtures/set013/scoper.inc.php index e837ffee..53b3a8de 100644 --- a/fixtures/set013/scoper.inc.php +++ b/fixtures/set013/scoper.inc.php @@ -5,12 +5,8 @@ use Isolated\Symfony\Component\Finder\Finder; return [ - 'global_namespace_whitelist' => [ - 'AppKernel', - function (string $className): bool { - return 'PHPUnit' === substr($className, 0, 6); - }, - ], + // The prefix configuration. If a non null value will be used, a random prefix will be generated. + 'prefix' => null, // By default when running php-scoper add-prefix, it will prefix all relevant code found in the current working // directory. You can however define which files should be scoped by defining a collection of Finders in the @@ -45,25 +41,12 @@ function (string $className): bool { // For more see: https://github.com/humbug/php-scoper#patchers 'patchers' => [ function (string $filePath, string $prefix, string $contents): string { - // Change the content here. + // Change the contents here. return $contents; }, ], - // By default, PHP-Scoper only prefixes code where the namespace is non-global. In other words, non-namespaced - // code is not prefixed. This leaves the majority of classes, functions and constants in PHP - and most extensions, - // untouched. - // - // This is not necessarily a desirable outcome for vendor dependencies which are also not namespaced. To ensure - // they are isolated, you can configure the following which can be a list of strings or callables taking a string - // (the class name) as an argument and return a boolean (true meaning the class is going to prefixed). - // - // For more, see https://github.com/humbug/php-scoper#global-namespace-whitelisting - 'global_namespace_whitelist' => [ - 'AppKernel', - ], - // PHP-Scoper's goal is to make sure that all code for a project lies in a distinct PHP namespace. However, you // may want to share a common API between the bundled code of your PHAR and the consumer code. For example if // you have a PHPUnit PHAR with isolated code, you still want the PHAR to be able to understand the @@ -74,6 +57,18 @@ function (string $filePath, string $prefix, string $contents): string { // // Fore more see https://github.com/humbug/php-scoper#whitelist 'whitelist' => [ - 'PHPUnit\Framework\TestCase', + // 'PHPUnit\Framework\TestCase', // A specific class + // 'PHPUnit\Framework\*', // The whole namespace + // '*', // Everything ], + + // If `true` then the user defined constants belonging to the global namespace will not be prefixed. + // + // For more see https://github.com/humbug/php-scoper#constants-from-the-global-namespace-whitelisting + 'whitelist-global-constants' => true, + + // If `true` then the user defined functions belonging to the global namespace will not be prefixed. + // + // For more see https://github.com/humbug/php-scoper#global-user-functions + 'whitelist-global-functions' => true, ]; diff --git a/specs/class-const/global-scope-single-level-with-single-level-use-statement-and-alias.php b/specs/class-const/global-scope-single-level-with-single-level-use-statement-and-alias.php index a4be2f33..3a7e3fee 100644 --- a/specs/class-const/global-scope-single-level-with-single-level-use-statement-and-alias.php +++ b/specs/class-const/global-scope-single-level-with-single-level-use-statement-and-alias.php @@ -22,13 +22,7 @@ 'whitelist-global-functions' => true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the use statement (cf. class belonging to the global scope tests) -- prefix the constant -SPEC - , + 'Constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace: -- do not prefix the class (cf. class belonging to the global scope tests) -- do nothing -SPEC - , + 'FQ constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the use statement (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -- transform the call into a FQ call -SPEC - , + 'Constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the use statement (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -- do nothing -SPEC - , + 'FQ constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a class which is imported via a use statement and which belongs to the global namespace: -- do not prefix the use statement (cf. class belonging to the global scope tests) -- transforms the call into a FQ call -SPEC - , + 'Constant call on a class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a class which is imported via a use statement and which belongs to the global namespace: -- do not prefix the use statement (cf. class belonging to the global scope tests) -- do nothing -SPEC - , + 'FQ constant call on a class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace: -- transform the call in a FQ call (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in - global whitelisted classes) -SPEC - , + 'Constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace: -- prefix the class (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global - whitelisted classes) -SPEC - , + 'FQ constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a class belonging to the global namespace: -- do not prefix the class (cf. class belonging to the global scope tests) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a class belonging to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a class belonging to the global namespace: -- do not prefix the class (cf. class belonging to the global scope tests) -- do not touch the call -SPEC - , + 'FQ constant call on a class belonging to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted class belonging to the global namespace: -- prefix the class (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a whitelisted class belonging to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted class belonging to the global namespace: -- prefix the class (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a whitelisted class belonging to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a namespaced class partially imported with an aliased use statement: -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class partially imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a namespaced class imported with an aliased use statement: -- prefix the use statement -- transform the call into a FQ call -SPEC - , + 'Constant call on a namespaced class imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a namespaced class imported with an aliased use statement: -- prefix the class only (not the use statement, cf. tests related to classes belonging to the global namespace) -SPEC - , + 'FQ constant call on a namespaced class imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ Constant call on a whitelisted namespaced class partially imported with an aliased use statement: -- do not prefix the class neither the use statement -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ Constant call on a whitelisted namespaced class partially imported with an aliased use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted namespaced class imported with an aliased use statement: -- prefix the class only (not the use statement, cf. tests related to classes belonging to the global namespace) -SPEC - , + 'FQ constant call on a whitelisted namespaced class imported with an aliased use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a namespaced class partially imported with a use statement: -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class partially imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a namespaced class imported with a use statement: -- prefix the use statement -- transform the call into a FQ call -SPEC - , + 'Constant call on a namespaced class imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a namespaced class imported with a use statement: -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a namespaced class imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ Constant call on a whitelisted namespaced class partially imported with a use statement: -- do not prefix the class neither the use statement -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ Constant call on a whitelisted namespaced class partially imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted namespaced class imported with a use statement: -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a whitelisted namespaced class imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a namespaced class: -- prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a namespaced class: -- prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a namespaced class' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted namespaced class: -- do not prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a whitelisted namespaced class' => [ 'whitelist' => ['PHPUnit\Command'], 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted namespaced class: -- do not prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a whitelisted namespaced class' => [ 'whitelist' => ['PHPUnit\Command'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the namespace -- do not prefix the use statement (cf. class belonging to the global scope tests) -- transform the call into a FQ call -SPEC - , + 'Constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the namespace -- do not prefix the class (cf. class belonging to the global scope tests) -SPEC - , + 'FQ constant call on a aliased class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the namespace -- prefix the use statement (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -- transform the call into a FQ call -SPEC - , + 'Constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace: -- prefix the namespace -- prefix the use statement (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -- do not touch the call -SPEC - , + 'FQ constant call on a whitelisted class which is imported via an aliased use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a class belonging to the global namespace or the current namespace: -- prefix the namespace -- transform the call into a FQ call -SPEC - , + 'Constant call on a class belonging to the global namespace or the current namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a class belonging to the global namespace or the current namespace: -- prefix the namespace -- do not touch the call -SPEC - , + 'FQ constant call on a class belonging to the global namespace or the current namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted class belonging to the global namespace: -- prefix the namespace -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a whitelisted class belonging to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a namespaced class partially imported with an aliased use statement: -- prefix the namespace -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class partially imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a namespaced class imported with an aliased use statement: -- prefix the namespace -- prefix the use statement -- transform the call into a FQ call -SPEC - , + 'Constant call on a namespaced class imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a namespaced class imported with an aliased use statement: -- prefix the namespace -- prefix the class only (not the use statement, cf. tests related to classes from the global scope) -SPEC - , + 'FQ constant call on a namespaced class imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ Constant call on a whitelisted namespaced class partially imported with an aliased use statement: -- prefix the namespace -- do not prefix the class neither the use statement -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ Constant call on a whitelisted namespaced class partially imported with an aliased use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a namespaced class partially imported with a use statement: -- prefix the namespace -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class partially imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a namespaced class imported with a use statement: -- prefix the namespace -- prefix the use statement -- transform the call into a FQ call -SPEC - , + 'Constant call on a namespaced class imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a namespaced class imported with a use statement: -- prefix the namespace -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a namespaced class imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ Constant call on a whitelisted namespaced class partially imported with a use statement: -- prefix the namespace -- do not prefix the class neither the use statement -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ Constant call on a whitelisted namespaced class partially imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted namespaced class imported with a use statement: -- prefix the namespace -- prefix the class only (not the use statement) -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a whitelisted namespaced class imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a namespaced class: -- prefix the namespace -- prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a namespaced class: -- prefix the namespace -- prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a namespaced class' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted namespaced class: -- prefix the namespace -- do not prefix the class -- transform the call into a FQ call -SPEC - , + 'Constant call on a whitelisted namespaced class' => [ 'whitelist' => ['X\PHPUnit\Command'], 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a namespaced class belonging to a whitelisted namespace: -- prefix the namespace -- prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class belonging to a whitelisted namespace' => [ 'whitelist' => ['X\PHPUnit\*'], 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a namespaced class belonging to a whitelisted namespace (2): -- prefix the namespace -- prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'Constant call on a namespaced class belonging to a whitelisted namespace (2)' => [ 'whitelist' => ['\*'], 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted namespaced class: -- prefix the namespace -- do not prefix the class -- transforms the call into a FQ call to avoid autoloading issues -SPEC - , + 'FQ constant call on a whitelisted namespaced class' => [ 'whitelist' => ['PHPUnit\Command'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -Constant call on a class which is imported via a use statement and which belongs to the global namespace: -- prefix the namespace -- do not prefix the use statement (cf. class belonging to the global scope tests) -- transforms the call into a FQ call -SPEC - , + 'Constant call on a class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a class which is imported via a use statement and which belongs to the global namespace: -- prefix the namespace -- do not prefix the use statement (cf. class belonging to the global scope tests) -- do nothing -SPEC - , + 'FQ constant call on a class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -Constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace: -- prefix the namespace -- transform the call in a FQ call (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -SPEC - , + 'Constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace: -- prefix the namespace -- prefix the class (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global whitelisted classes) -SPEC - , + 'FQ constant call on a whitelisted class which is imported via a use statement and which belongs to the global namespace' => [ 'payload' => <<<'PHP' true, ], - 'Declaration in the global namespace: add prefixed namespace' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' [ + 'Declaration in the global namespace with the global namespace which is namespaced whitelisted' => [ 'whitelist' => ['\*'], 'payload' => <<<'PHP' <<<'SPEC' -Declaration of a whitelisted class in the global namespace: -- add prefixed namespace -- append class alias statement to the class declaration -SPEC - , + 'Declaration of a whitelisted class in the global namespace' => [ 'whitelist' => ['A'], 'payload' => <<<'PHP' [ + 'Declaration of a whitelisted class in the global namespace which is whitelisted' => [ 'whitelist' => ['A', '\*'], 'payload' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace' => <<<'PHP' <<<'SPEC' -Declaration of a whitelisted class in the global namespace: -- add prefixed namespace -- append class alias statement to the class declaration -SPEC - , - 'whitelist' => ['A'], - 'payload' => <<<'PHP' - [ + 'Declaration in a whitelisted namespace' => [ 'whitelist' => ['Foo\*'], 'payload' => <<<'PHP' [ + 'Declaration of a whitelisted class in a namespace' => [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' [ + 'Declaration of a whitelisted class in a namespace with FQCNfor the whitelist' => [ 'whitelist' => ['\Foo\A'], 'payload' => <<<'PHP' [ + 'Declaration in the global namespace which is whitelisted' => [ 'whitelist' => ['\*'], 'payload' => <<<'PHP' <<<'SPEC' -Declaration in the global namespace with some whitelisted classes: -- add prefixed namespace -- prefix the classes -- append the class alias statements for the whitelisted class declarations -SPEC - , + 'Declaration in the global namespace with some whitelisted classes' => [ 'whitelist' => ['A', 'C'], 'payload' => <<<'PHP' <<<'PHP' + 'Multiple declarations in different namespaces' => <<<'PHP' true, ], - 'Declaration in the global namespace: warp in a prefixed namespace.' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' <<<'SPEC' -Declaration of a whitelisted class in the global namespace: warp in a prefixed namespace. - -TODO: unsupported at the moment. The `class_alias` statement appended to support whitelisted classes are added at the -end of a namespace statement for now. This could be supported if they are added right after the declaration statement -instead. -SPEC - , + 'Declaration of a whitelisted class in the global namespace' => [ 'whitelist' => ['A'], 'payload' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace: prefix each namespace' => <<<'PHP' [ + 'Declaration of a whitelisted class' => [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' <<<'PHP' + 'Multiple declarations in different namespaces' => <<<'PHP' true, ], - 'Declaration in the global namespace: add prefixed namespace.' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace' => <<<'PHP' [ + 'Declaration of a whitelisted final class' => [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' <<<'PHP' + 'Multiple declarations in different namespaces' => <<<'PHP' true, ], - 'Declaration in the global namespace: add a prefixed namespace.' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace' => <<<'PHP' [ + 'Declaration of a whitelisted interface' => [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' <<<'PHP' + 'Multiple declarations in different namespaces' => <<<'PHP' true, ], - 'Declaration in the global namespace: prefix only non-internal classes.' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace' => <<<'PHP' [ + 'Declaration of a whitelisted class' => [ 'whitelist' => ['Foo\B'], 'payload' => <<<'PHP' <<<'PHP' + 'Declaration in a different namespace imported via a use statement' => <<<'PHP' <<<'PHP' + 'Declaration in a different namespace imported via a FQ call' => <<<'PHP' true, ], - 'Declaration in the global namespace: add a prefixed namespace.' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace' => <<<'PHP' [ + 'Declaration of a whitelisted class' => [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' [ + 'Declaration of a whitelisted class whitelisted with a pattern' => [ 'whitelist' => ['Foo\A*'], 'payload' => <<<'PHP' <<<'PHP' + 'Multiple declarations in different namespaces' => <<<'PHP' [ + 'Multiple declarations in different namespaces with whitelisted class' => [ 'whitelist' => [ 'Foo\A', 'Bar\B', diff --git a/specs/class/trait.php b/specs/class/trait.php index a2e0b74f..02c6fb6d 100644 --- a/specs/class/trait.php +++ b/specs/class/trait.php @@ -22,7 +22,7 @@ 'whitelist-global-functions' => true, ], - 'Declaration in the global namespace: add prefixed namespace.' => <<<'PHP' + 'Declaration in the global namespace' => <<<'PHP' <<<'PHP' + 'Declaration in a namespace' => <<<'PHP' [ + 'Declaration of a whitelisted trait' => [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' <<<'PHP' + 'Multiple declarations in different namespaces' => <<<'PHP' [ - 'title' => 'Global constant usage in the global scope', + 'title' => 'Global constant declaration in the global scope', // Default values. If not specified will be the one used 'prefix' => 'Humbug', 'whitelist' => [], @@ -40,7 +40,7 @@ const FOO_CONST = \Humbug\foo(); const X = 'x', Y = ''; -\define('BAR_CONST', \Humbug\foo()); +\define('Humbug\\BAR_CONST', \Humbug\foo()); \define('Humbug\\Acme\\BAR_CONST', \Humbug\foo()); \define(\Humbug\FOO_CONST, \Humbug\foo()); \define(\Humbug\FOO_CONST, \Humbug\foo()); @@ -101,7 +101,7 @@ \define('FOO_CONST', \Humbug\foo()); const X = 'x', Y = ''; \define('BAR_CONST', \Humbug\foo()); -\define('BAP_CONST', \Humbug\foo()); +\define('Humbug\\BAP_CONST', \Humbug\foo()); \define('Acme\\BAR_CONST', \Humbug\foo()); \define(\FOO_CONST, \Humbug\foo()); \define(\FOO_CONST, \Humbug\foo()); @@ -142,7 +142,7 @@ const FOO_CONST = foo(); const X = 'x', Y = ''; -\define('BAR_CONST', foo()); +\define('Humbug\\BAR_CONST', foo()); \define('Humbug\\Acme\\BAR_CONST', foo()); \define(FOO_CONST, foo()); \define(\Humbug\FOO_CONST, foo()); @@ -172,7 +172,7 @@ const FOO_CONST = foo(); const X = 'x', Y = ''; -\define('BAR_CONST', foo()); +\define('Humbug\\BAR_CONST', foo()); \define('Acme\\BAR_CONST', foo()); \define(FOO_CONST, foo()); \define(\Humbug\FOO_CONST, foo()); @@ -202,7 +202,7 @@ const FOO_CONST = foo(); const X = 'x', Y = ''; -\define('BAR_CONST', foo()); +\define('Humbug\\BAR_CONST', foo()); \define('Acme\\BAR_CONST', foo()); \define(FOO_CONST, foo()); \define(\Humbug\FOO_CONST, foo()); diff --git a/specs/new/global-scope-single-part-with-single-level-use-statement-an-alias.php b/specs/new/global-scope-single-part-with-single-level-use-statement-an-alias.php index 5532742c..33358e43 100644 --- a/specs/new/global-scope-single-part-with-single-level-use-statement-an-alias.php +++ b/specs/new/global-scope-single-part-with-single-level-use-statement-an-alias.php @@ -22,13 +22,7 @@ 'whitelist-global-functions' => true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class belonging to the global namespace imported via an aliased use statement: -- prefix the namespace, use and new statements -- transform the call into a FQ call -SPEC - , + 'New statement call of a class belonging to the global namespace imported via an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a class belonging to the global namespace imported via an aliased use statement; the call is made -using the original class instead of the alias -- prefix the namespace, use and new statements -- transform the call into a FQ call -SPEC - , + 'New statement call of a class belonging to the global namespace imported via an aliased use statement; the call is made using the original class instead of the alias' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class belonging to the global namespace imported via an aliased use statement: -- prefix the namespace, use and new statements -SPEC - , + 'FQ new statement call of a class belonging to the global namespace imported via an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class belonging to the global namespace imported via an aliased use statement; the new -statement uses the class directly instead of the alias -- prefix the namespace, use and new statements -SPEC - , + 'FQ new statement call of a class belonging to the global namespace imported via an aliased use statement; the new statement uses the class directly instead of the alias' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of an internal class imported with an aliased use statement: -- wrap the call in a prefixed namespace -- do not prefix the use and new statement -- transform the call into a FQ call -SPEC - , + 'New statement call of an internal class imported with an aliased use statement' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class belonging to the global namespace imported via a use statement: -- prefix the namespace, use and new statements -- transform the call into a FQ call -SPEC - , + 'New statement call of a class belonging to the global namespace imported via a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class belonging to the global namespace imported via a use statement: -- prefix the namespace, use and new statements -SPEC - , + 'FQ new statement call of a class belonging to the global namespace imported via a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of an internal class: -- wrap the call in a prefixed namespace -- do not prefix the use and new statement -- transform the call into a FQ call -SPEC - , + 'New statement call of an internal class' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class belonging to the global namespace: -- wrap everything in a prefixed namespace -- prefix the class -- transform the class in a FQCN call -prefix it -SPEC - , + 'New statement call of a class belonging to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a class belonging to the global namespace: -- wrap everything in a prefixed namespace -- do not prefix the class -- transform the class in a FQCN call -SPEC - , + 'New statement call of a class belonging to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class belonging to the global namespace: -- wrap everything in a prefixed namespace -- prefix the class -SPEC - , + 'FQ new statement call of a class belonging to the global namespace' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of an unknown class belonging to the global namespace: -- wrap everything in a prefixed namespace -- prefix the class -- transform the class in a FQCN call -prefix it -SPEC - , + 'New statement call of an unknown class belonging to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a namespaced class partially imported with an aliased use statement: -- prefix the namespaces, use statement and the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a namespaced class partially imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a namespaced class imported with an aliased use statement: -- prefix the namespaces, use statement and the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a namespaced class imported with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a namespaced class with an aliased use statement: -- prefix the namespaces, use statement and the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a namespaced class with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class with an aliased use statement: -- prefix the namespaces, use statement and the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a class with an aliased use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a whitelisted namespaced class partially imported with an aliased use statement: -- prefix each namespace and the call -- append the class_alias statement to the whitelisted class -- transform the call into a FQ call -SPEC - , + 'New statement call of a whitelisted namespaced class partially imported with an aliased use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a whitelisted namespaced class imported with an aliased use statement: -- prefix the namespaces, use statement and the call -- append the class_alias statement to the whitelisted class -- transform the call into a FQ call -SPEC - , + 'New statement call of a whitelisted namespaced class imported with an aliased use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a namespaced class partially imported with a use statement: -- prefix the namespaces and the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a namespaced class partially imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a whitelisted namespaced class partially imported with a use statement: -- prefix the namespaces and the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a whitelisted namespaced class partially imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a namespaced class partially imported with a use statement: -- prefix the namespaces and the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a namespaced class partially imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a whitelisted namespaced class partially imported with a use statement: -- prefix the namespaces and the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a whitelisted namespaced class partially imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a namespaced class imported with a use statement: -- prefix the namespaces and the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a namespaced class imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a whitelisted namespaced class imported with a use statement: -- prefix the namespaces and the call -- do not prefix the use statement of the whitelisted class -- append the class_alias statement to the class -- transform the call into a FQ call -SPEC - , + 'New statement call of a whitelisted namespaced class imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a namespaced class imported with a use statement: -- prefix the namespaces and the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a namespaced class imported with a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a whitelisted namespaced class imported with a use statement: -- prefix the namespaces and the call -- do not prefix the use statement of the whitelisted class -- append the class_alias statement to the class -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a whitelisted namespaced class imported with a use statement' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a namespaced class: -- prefix the namespace -- prefix the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a namespaced class' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a namespaced class: -- prefix the namespace -- prefix the call -SPEC - , + 'FQ new statement call of a namespaced class' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a whitelisted namespaced class: -- prefix the namespace -- append the class_alias for the whitelisted class -- prefix the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a whitelisted namespaced class' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a whitelisted namespaced class: -- prefix the namespace -- append the class_alias for the whitelisted class -- prefix the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a whitelisted namespaced class' => [ 'whitelist' => ['Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class: -- prefix the namespace -- do not prefix the call: see tests related to the classes belonging to the global namespace -- transform the call into a FQ call -SPEC - , + 'New statement call of a class' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class: -- prefix the namespace -- do not prefix the call: see tests related to the classes belonging to the global namespace -SPEC - , + 'FQ new statement call of a class' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class: -- prefix the namespace -- prefix the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a class' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class belonging to the global namespace: -- prefix the namespace -- do not prefix the call -SPEC - , + 'FQ new statement call of a class belonging to the global namespace' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class belonging to the global namespace imported via a use statement: -- do not touch the use statement (see tests related to the use statements of a class belonging to the global scope) -- transform the call into a FQ call -SPEC - , + 'New statement call of a class belonging to the global namespace imported via a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class belonging to the global namespace imported via a use statement: -- do not touch the use statement (see tests related to the use statements of a class belonging to the global scope) -SPEC - , + 'FQ new statement call of a class belonging to the global namespace imported via a use statement' => [ 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class via a use statement: -- prefix the namespace -- prefix the use statement -- prefix the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a class via a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class via a use statement: -- prefix the namespace -- prefix the use statement -- prefix the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a class via a use statement' => [ 'payload' => <<<'PHP' <<<'SPEC' -New statement call of a whitelisted class via a use statement: -- prefix the namespace -- prefix the use statement -- do not prefix the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a whitelisted class via a use statement' => [ 'whitelist' => ['X\Foo\Bar'], 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a non-whitelisted class via a use statement: -- prefix the namespace -- prefix the use statement -- prefix the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a non-whitelisted class via a use statement' => [ 'whitelist' => ['X\Foo\Bar'], 'payload' => <<<'PHP' true, ], - [ - 'spec' => <<<'SPEC' -New statement call of a class: -- prefix the namespace -- prefix the call -- transform the call into a FQ call -SPEC - , + 'New statement call of a class' => [ 'payload' => <<<'PHP' <<<'SPEC' -FQ new statement call of a class: -- prefix the namespace -- prefix the call -- transform the call into a FQ call -SPEC - , + 'FQ new statement call of a class' => [ 'payload' => <<<'PHP' '\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', '\\Humbug\\Symfony\\Component\\Yaml\\Yaml' => '\\Humbug\\Symfony\\Component\\Yaml\\_Yaml', + 'Closure', + 'usedAttributes', + 'FOO', + 'PHP_EOL', ]; +(new X)->foo()([ + 'Symfony\\Component\\Yaml\\Yaml' => 'Symfony\\Component\\Yaml\\_Yaml', + '\\Symfony\\Component\\Yaml\\Yaml' => '\\Symfony\\Component\\Yaml\\_Yaml', + 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', + '\\Humbug\\Symfony\\Component\\Yaml\\Yaml' => '\\Humbug\\Symfony\\Component\\Yaml\\_Yaml', + 'Closure', + 'usedAttributes', + 'FOO', + 'PHP_EOL', +]); + ---- 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml']; +$x = ['Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Closure', 'usedAttributes', 'FOO', 'PHP_EOL']; +(new \Humbug\X())->foo()(['Symfony\\Component\\Yaml\\Yaml' => 'Symfony\\Component\\Yaml\\_Yaml', '\\Symfony\\Component\\Yaml\\Yaml' => '\\Symfony\\Component\\Yaml\\_Yaml', 'Humbug\\Symfony\\Component\\Yaml\\Yaml' => 'Humbug\\Symfony\\Component\\Yaml\\_Yaml', '\\Humbug\\Symfony\\Component\\Yaml\\Yaml' => '\\Humbug\\Symfony\\Component\\Yaml\\_Yaml', 'Closure', 'usedAttributes', 'FOO', 'PHP_EOL']); PHP , diff --git a/specs/string-literal/const.php b/specs/string-literal/const.php index 7dcbe81e..5877aa9c 100644 --- a/specs/string-literal/const.php +++ b/specs/string-literal/const.php @@ -22,9 +22,13 @@ 'whitelist-global-functions' => true, ], - 'FQCN string argument: transform into a FQCN and prefix it' => <<<'PHP' + 'FQCN string argument' => <<<'PHP' [ + 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['Symfony\Component\*'], + 'payload' => <<<'PHP' + <<<'PHP' + <<<'PHP' + [ + 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'payload' => <<<'PHP' + true, ], - 'FQCN string argument: transform into a FQCN and prefix it' => <<<'PHP' + 'FQCN string argument' => <<<'PHP' true, ], - 'FQCN string argument: transform into a FQCN and prefix it' => <<<'PHP' + 'FQCN string argument' => <<<'PHP' [ + 'FQCN string argument on whitelisted class' => [ 'whitelist' => ['Symfony\Component\Yaml\Yaml', 'Swift'], 'payload' => <<<'PHP' <<<'PHP' + 'FQCN string argument with global functions not whitelisted' => [ + 'whitelist-global-functions' => false, + 'payload' => <<<'PHP' +colorize)('fg-green', '✔'); +($this->colorize)(['Soft', 'autoload']); + +---- +colorize)('fg-green', '✔'); +($this->colorize)(['Soft', 'autoload']); + +PHP + ], + + 'FQCN string argument formed by concatenated strings' => <<<'PHP' <<<'PHP' + 'FQC constant call' => <<<'PHP' [ + 'FQC constant call on whitelisted class' => [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], 'payload' => <<<'PHP' true, ], - 'FQCN string argument: transform into a FQCN and prefix it' => <<<'PHP' + 'FQCN string argument' => <<<'PHP' foo('Symfony\\Component\\Yaml\\Yaml'); +(new X())->foo('Humbug\\Symfony\\Component\\Yaml\\Yaml', $y = 'Foo'); + +$x = new X(); + +$x->foo()('Humbug\\Symfony\\Component\\Yaml\\Yaml', $y = 'Foo'); ---- foo('Humbug\\Symfony\\Component\\Yaml\\Yaml'); +(new \Humbug\X())->foo('Humbug\\Symfony\\Component\\Yaml\\Yaml', $y = 'Foo'); +$x = new \Humbug\X(); +$x->foo()('Humbug\\Symfony\\Component\\Yaml\\Yaml', $y = 'Foo'); PHP , - 'FQCN string argument with a static method: transform into a FQCN and prefix it' => <<<'PHP' + 'FQCN string argument with a static method' => <<<'PHP' , + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'meta' => [ + 'title' => 'String literal used as a new statement argument', + // Default values. If not specified will be the one used + 'prefix' => 'Humbug', + 'whitelist' => [], + 'whitelist-global-constants' => true, + 'whitelist-global-functions' => true, + ], + + 'FQCN string argument' => <<<'PHP' + [ + 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['Symfony\Component\*'], + 'payload' => <<<'PHP' + <<<'PHP' + <<<'PHP' + [ + 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'payload' => <<<'PHP' + true, ], - 'FQCN string argument: transform into a FQCN and prefix it' => <<<'PHP' + 'FQCN string argument' => <<<'PHP' <<<'PHP' + 'Invalid FQCN strings' => <<<'PHP' [ + 'FQCN string argument on whitelisted class' => [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], 'payload' => <<<'PHP' [ + 'FQCN string argument on classes belonging to a whitelisted namespace' => [ 'whitelist' => ['Symfony\Component\*'], 'payload' => <<<'PHP' <<<'PHP' + 'FQCN string argument formed by concatenated strings' => <<<'PHP' <<<'PHP' + 'FQC constant call' => <<<'PHP' [ + 'FQC constant call on whitelisted class' => [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], 'payload' => <<<'PHP' [ - 'title' => 'Class declaration', + 'title' => 'Whitelist case sensitiveness', // Default values. If not specified will be the one used 'prefix' => 'Humbug', 'whitelist' => [], @@ -57,6 +57,7 @@ public function foo() const FOO = 'foo'; define('Acme\BAR', 'bar'); +echo \Acme\BAR; ---- slice(0, -1); @@ -169,11 +168,11 @@ function %1$s() { } PHP , - $original->toString(), - $alias->toString() + $original, + $alias ); }, - iterator_to_array($whitelistedFunctions) + $whitelistedFunctions ); if ([] === $statements) { @@ -191,14 +190,14 @@ function %1$s() { return $statements; } - private function hasNamespacedFunctions(WhitelistedFunctionCollection $functions): bool + private function hasNamespacedFunctions(array $functions): bool { foreach ($functions as [$original, $alias]) { - /* - * @var FullyQualified $original - * @var FullyQualified $alias + /** + * @var string + * @var string $alias */ - if (count($original->parts) > 1) { + if (false !== strpos($original, '\\')) { return true; } } diff --git a/src/PhpParser/Node/ClassAliasFuncCall.php b/src/PhpParser/Node/ClassAliasFuncCall.php new file mode 100644 index 00000000..1c7b2704 --- /dev/null +++ b/src/PhpParser/Node/ClassAliasFuncCall.php @@ -0,0 +1,48 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Node; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Scalar\String_; + +final class ClassAliasFuncCall extends FuncCall +{ + /** + * @inheritdoc + */ + public function __construct(FullyQualified $prefixedName, FullyQualified $originalName, array $attributes = []) + { + parent::__construct( + new FullyQualified('class_alias'), + [ + new Arg( + new String_((string) $prefixedName) + ), + new Arg( + new String_((string) $originalName) + ), + new Arg( + new ConstFetch( + new FullyQualified('false') + ) + ), + ], + $attributes + ); + } +} diff --git a/src/PhpParser/Node/PrefixedName.php b/src/PhpParser/Node/PrefixedName.php new file mode 100644 index 00000000..2514a34e --- /dev/null +++ b/src/PhpParser/Node/PrefixedName.php @@ -0,0 +1,44 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Node; + +use PhpParser\Node\Name\FullyQualified; + +final class PrefixedName extends FullyQualified +{ + private $prefixedName; + private $originalName; + + /** + * @inheritdoc + */ + public function __construct(FullyQualified $prefixedName, FullyQualified $originalName, array $attributes = []) + { + parent::__construct($prefixedName, $attributes); + + $this->prefixedName = new FullyQualified($prefixedName, $attributes); + $this->originalName = new FullyQualified($originalName, $attributes); + } + + public function getPrefixedName(): FullyQualified + { + return $this->prefixedName; + } + + public function getOriginalName(): FullyQualified + { + return $this->originalName; + } +} diff --git a/src/PhpParser/NodeVisitor/ClassAliasStmtAppender.php b/src/PhpParser/NodeVisitor/ClassAliasStmtAppender.php new file mode 100644 index 00000000..116dd9ab --- /dev/null +++ b/src/PhpParser/NodeVisitor/ClassAliasStmtAppender.php @@ -0,0 +1,144 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\NodeVisitor; + +use Humbug\PhpScoper\PhpParser\Node\ClassAliasFuncCall; +use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver; +use Humbug\PhpScoper\Whitelist; +use PhpParser\Node; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Expression; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\NodeVisitorAbstract; +use function array_reduce; + +/** + * Appends a `class_alias` to the whitelisted classes. + * + * ``` + * namespace A; + * + * class Foo + * { + * } + * ``` + * + * => + * + * ``` + * namespace Humbug\A; + * + * class Foo + * { + * } + * + * class_alias('Humbug\A\Foo', 'A\Foo', false); + * ``` + * + * @internal + */ +final class ClassAliasStmtAppender extends NodeVisitorAbstract +{ + private $prefix; + private $whitelist; + private $nameResolver; + + public function __construct(string $prefix, Whitelist $whitelist, FullyQualifiedNameResolver $nameResolver) + { + $this->prefix = $prefix; + $this->whitelist = $whitelist; + $this->nameResolver = $nameResolver; + } + + /** + * @inheritdoc + */ + public function afterTraverse(array $nodes): array + { + $newNodes = []; + + foreach ($nodes as $node) { + if ($node instanceof Namespace_) { + $node = $this->appendToNamespaceStmt($node); + } + + $newNodes[] = $node; + } + + return $newNodes; + } + + private function appendToNamespaceStmt(Namespace_ $namespace): Namespace_ + { + $namespace->stmts = array_reduce( + $namespace->stmts, + [$this, 'createNamespaceStmts'], + [] + ); + + return $namespace; + } + + /** + * @return Stmt[] + */ + private function createNamespaceStmts(array $stmts, Stmt $stmt): array + { + $stmts[] = $stmt; + + if (false === ($stmt instanceof Class_ || $stmt instanceof Interface_)) { + return $stmts; + } + /** @var Class_|Interface_ $stmt */ + if (null === $stmt->name) { + return $stmts; + } + + $originalName = $this->nameResolver->resolveName($stmt->name)->getName(); + + if (false === ($originalName instanceof FullyQualified) + || $this->whitelist->belongsToWhitelistedNamespace((string) $originalName) + || false === $this->whitelist->isSymbolWhitelisted((string) $originalName) + ) { + return $stmts; + } + /* @var FullyQualified $originalName */ + + $stmts[] = $this->createAliasStmt($originalName, $stmt); + + return $stmts; + } + + private function createAliasStmt(FullyQualified $originalName, Node $stmt): Expression + { + $call = new ClassAliasFuncCall( + FullyQualified::concat($this->prefix, $originalName), + $originalName, + $stmt->getAttributes() + ); + + $expression = new Expression( + $call, + $stmt->getAttributes() + ); + + $call->setAttribute(ParentNodeAppender::PARENT_ATTRIBUTE, $expression); + + return $expression; + } +} diff --git a/src/PhpParser/NodeVisitor/Collection/NamespaceStmtCollection.php b/src/PhpParser/NodeVisitor/Collection/NamespaceStmtCollection.php index abc7752a..0ffbe902 100644 --- a/src/PhpParser/NodeVisitor/Collection/NamespaceStmtCollection.php +++ b/src/PhpParser/NodeVisitor/Collection/NamespaceStmtCollection.php @@ -16,7 +16,7 @@ use ArrayIterator; use Countable; -use Humbug\PhpScoper\PhpParser\NodeVisitor\AppendParentNode; +use Humbug\PhpScoper\PhpParser\NodeVisitor\ParentNodeAppender; use IteratorAggregate; use PhpParser\Node; use PhpParser\Node\Name; @@ -86,11 +86,11 @@ public function count(): int private function getNodeNamespace(Node $node): ?Name { - if (false === AppendParentNode::hasParent($node)) { + if (false === ParentNodeAppender::hasParent($node)) { return null; } - $parentNode = AppendParentNode::getParent($node); + $parentNode = ParentNodeAppender::getParent($node); if ($parentNode instanceof Namespace_) { return $this->mapping[(string) $parentNode->name]; diff --git a/src/PhpParser/NodeVisitor/Collection/UseStmtCollection.php b/src/PhpParser/NodeVisitor/Collection/UseStmtCollection.php index ae6fcec5..b0f87516 100644 --- a/src/PhpParser/NodeVisitor/Collection/UseStmtCollection.php +++ b/src/PhpParser/NodeVisitor/Collection/UseStmtCollection.php @@ -15,11 +15,12 @@ namespace Humbug\PhpScoper\PhpParser\NodeVisitor\Collection; use ArrayIterator; -use Humbug\PhpScoper\PhpParser\NodeVisitor\AppendParentNode; +use Humbug\PhpScoper\PhpParser\NodeVisitor\ParentNodeAppender; use IteratorAggregate; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; +use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; use function Humbug\PhpScoper\clone_node; @@ -64,35 +65,49 @@ public function findStatementForNode(?Name $namespaceName, Name $node): ?Name { $name = strtolower($node->getFirst()); - $parentNode = AppendParentNode::findParent($node); + $parentNode = ParentNodeAppender::findParent($node); + + if ($parentNode instanceof ClassLike + && $node->hasAttribute('original_node') + && $node->getAttribute('original_node') === $parentNode->name + ) { + // The current node can either be the class like name or one of its elements, e.g. extends or implements. + // In the first case, the node was original an Identifier. + + return null; + } $useStatements = $this->nodes[(string) $namespaceName] ?? []; foreach ($useStatements as $use_) { foreach ($use_->uses as $useStatement) { - if ($useStatement instanceof UseUse) { - if ($name === $useStatement->getAlias()->toLowerString()) { - if ($parentNode instanceof FuncCall && 1 === count($node->parts)) { - if (Use_::TYPE_FUNCTION === $use_->type) { - return $useStatement->name; - } - - continue; + if (false === ($useStatement instanceof UseUse)) { + continue; + } + + if ($name === $useStatement->getAlias()->toLowerString()) { + if ($parentNode instanceof FuncCall && 1 === count($node->parts)) { + if (Use_::TYPE_FUNCTION === $use_->type) { + return $useStatement->name; } - if ($parentNode instanceof ConstFetch && 1 === count($node->parts)) { - if (Use_::TYPE_CONSTANT === $use_->type) { - return $useStatement->name; - } + continue; + } - continue; + if ($parentNode instanceof ConstFetch && 1 === count($node->parts)) { + if (Use_::TYPE_CONSTANT === $use_->type) { + return $useStatement->name; } - // Match the alias - return $useStatement->name; - } elseif (null !== $useStatement->alias) { continue; } + + // Match the alias + return $useStatement->name; + } + + if (null !== $useStatement->alias) { + continue; } } } diff --git a/src/PhpParser/NodeVisitor/ConstStmtReplacer.php b/src/PhpParser/NodeVisitor/ConstStmtReplacer.php index 25d6fcbd..24c383c0 100644 --- a/src/PhpParser/NodeVisitor/ConstStmtReplacer.php +++ b/src/PhpParser/NodeVisitor/ConstStmtReplacer.php @@ -21,13 +21,14 @@ use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Const_; use PhpParser\Node\Stmt\Expression; use PhpParser\NodeVisitorAbstract; use UnexpectedValueException; use function count; /** - * Replaces const declaration by define. + * Replaces const declaration by define when the constant is whitelisted. * * ``` * const DUMMY_CONST = 'foo'; @@ -55,11 +56,11 @@ public function __construct(Whitelist $whitelist, FullyQualifiedNameResolver $na /** * {@inheritdoc} * - * @param Node\Stmt\Const_ $node + * @param Const_ $node */ public function enterNode(Node $node): Node { - if (false === ($node instanceof Node\Stmt\Const_)) { + if (false === ($node instanceof Const_)) { return $node; } @@ -72,7 +73,7 @@ public function enterNode(Node $node): Node ) )->getName(); - if (false === $this->whitelist->isConstantWhitelisted((string) $resolvedConstantName)) { + if (false === $this->whitelist->isSymbolWhitelisted((string) $resolvedConstantName, true)) { continue; } diff --git a/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php b/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php index 30e27a59..dd7b2227 100644 --- a/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php +++ b/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php @@ -18,14 +18,12 @@ use Humbug\PhpScoper\Whitelist; use PhpParser\Node; use PhpParser\Node\Identifier; -use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Function_; use PhpParser\NodeVisitorAbstract; -use function count; /** - * Records the user functions registered in the global namespace. + * Records the user functions registered in the global namespace which have been whitelisted. * * @private */ @@ -50,30 +48,26 @@ public function __construct( */ public function enterNode(Node $node): Node { - if (false === ($node instanceof Identifier) || false === AppendParentNode::hasParent($node)) { + if (false === ($node instanceof Identifier) || false === ParentNodeAppender::hasParent($node)) { return $node; } - $parent = AppendParentNode::getParent($node); + $parent = ParentNodeAppender::getParent($node); if (false === ($parent instanceof Function_)) { return $node; } /** @var Identifier $node */ - $resolvedValue = $this->nameResolver->resolveName( - new Name( - $node->name, - $node->getAttributes() - ) - ); - $resolvedName = $resolvedValue->getName(); + $resolvedName = $this->nameResolver->resolveName($node)->getName(); - if ($resolvedName instanceof FullyQualified - && ( - (1 === count($resolvedName->parts) && $this->whitelist->whitelistGlobalFunctions()) - || $this->whitelist->isClassWhitelisted((string) $resolvedName) - ) + if (false === ($resolvedName instanceof FullyQualified)) { + return $node; + } + + /** @var FullyQualified $resolvedName */ + if ($this->whitelist->isGlobalWhitelistedFunction((string) $resolvedName) + || $this->whitelist->isSymbolWhitelisted((string) $resolvedName) ) { $this->whitelist->recordWhitelistedFunction( $resolvedName, diff --git a/src/PhpParser/NodeVisitor/NameStmtPrefixer.php b/src/PhpParser/NodeVisitor/NameStmtPrefixer.php index 7df40ee9..5f36b16b 100644 --- a/src/PhpParser/NodeVisitor/NameStmtPrefixer.php +++ b/src/PhpParser/NodeVisitor/NameStmtPrefixer.php @@ -35,10 +35,11 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Interface_; use PhpParser\NodeVisitorAbstract; -use function count; use function in_array; /** + * Prefixes names when appropriate. + * * ``` * new Foo\Bar(); * ```. @@ -81,7 +82,7 @@ public function __construct( */ public function enterNode(Node $node): Node { - return ($node instanceof Name && AppendParentNode::hasParent($node)) + return ($node instanceof Name && ParentNodeAppender::hasParent($node)) ? $this->prefixName($node) : $node ; @@ -89,14 +90,14 @@ public function enterNode(Node $node): Node private function prefixName(Name $name): Node { - $parentNode = AppendParentNode::getParent($name); + $parentNode = ParentNodeAppender::getParent($name); if ($parentNode instanceof NullableType) { - if (false === AppendParentNode::hasParent($parentNode)) { + if (false === ParentNodeAppender::hasParent($parentNode)) { return $name; } - $parentNode = AppendParentNode::getParent($parentNode); + $parentNode = ParentNodeAppender::getParent($parentNode); } if (false === ( @@ -142,25 +143,21 @@ private function prefixName(Name $name): Node $resolvedName = $resolvedValue->getName(); - // Skip if is already prefixed - if ($this->prefix === $resolvedName->getFirst()) { - return $resolvedName; - } - - // Skip if the node namespace is whitelisted - if ($this->whitelist->isNamespaceWhitelisted((string) $resolvedName)) { + if ($this->prefix === $resolvedName->getFirst() // Skip if is already prefixed + || $this->whitelist->belongsToWhitelistedNamespace((string) $resolvedName) // Skip if the namespace node is whitelisted + ) { return $resolvedName; } // Check if the class can be prefixed - if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)) { - if ($this->reflector->isClassInternal($resolvedName->toString())) { - return $resolvedName; - } + if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall) + && $this->reflector->isClassInternal($resolvedName->toString()) + ) { + return $resolvedName; } if ($parentNode instanceof ConstFetch) { - if ($this->whitelist->isConstantWhitelisted($resolvedName->toString())) { + if ($this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)) { return $resolvedName; } @@ -168,15 +165,18 @@ private function prefixName(Name $name): Node return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes()); } - // Constants have a fallback autoloading so we cannot prefix them when the name is ambiguous + // Constants have an autoloading fallback so we cannot prefix them when the name is ambiguous // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation if (false === ($resolvedName instanceof FullyQualified)) { return $resolvedName; } - if (1 === count($resolvedName->parts) && $this->whitelist->whitelistGlobalConstants()) { + if ($this->whitelist->isGlobalWhitelistedConstant((string) $resolvedName)) { + // Unlike classes & functions, whitelisted are not prefixed with aliases registered in scoper-autoload.php return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes()); } + + // Continue } // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous diff --git a/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php b/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php index 14016cf0..9e19b3ca 100644 --- a/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php +++ b/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php @@ -18,8 +18,7 @@ use Humbug\PhpScoper\Whitelist; use PhpParser\Node; use PhpParser\Node\Name; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeVisitorAbstract; use function Humbug\PhpScoper\clone_node; @@ -80,7 +79,7 @@ private function prefixNamespaceStmt(Namespace_ $namespace): Node private function isWhitelistedNode(Node $node): bool { - if (($node instanceof Class_ || $node instanceof Interface_)) { + if ($node instanceof ClassLike) { return true; } @@ -98,7 +97,7 @@ private function isWhitelistedNode(Node $node): bool private function shouldPrefixStmt(Namespace_ $namespace): bool { - if ($this->whitelist->isNamespaceWhitelisted((string) $namespace->name)) { + if ($this->whitelist->belongsToWhitelistedNamespace((string) $namespace->name)) { return false; } diff --git a/src/PhpParser/NodeVisitor/AppendParentNode.php b/src/PhpParser/NodeVisitor/ParentNodeAppender.php similarity index 93% rename from src/PhpParser/NodeVisitor/AppendParentNode.php rename to src/PhpParser/NodeVisitor/ParentNodeAppender.php index 68597b4f..f4e29d10 100644 --- a/src/PhpParser/NodeVisitor/AppendParentNode.php +++ b/src/PhpParser/NodeVisitor/ParentNodeAppender.php @@ -16,6 +16,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use function count; /** * Appends the parent node as an attribute to each node. This allows to have more context in the other visitors when @@ -24,9 +25,9 @@ * * @private */ -final class AppendParentNode extends NodeVisitorAbstract +final class ParentNodeAppender extends NodeVisitorAbstract { - private const PARENT_ATTRIBUTE = 'parent'; + public const PARENT_ATTRIBUTE = 'parent'; private $stack; diff --git a/src/PhpParser/NodeVisitor/Resolver/FullyQualifiedNameResolver.php b/src/PhpParser/NodeVisitor/Resolver/FullyQualifiedNameResolver.php index f1bf3f7a..59d73c53 100644 --- a/src/PhpParser/NodeVisitor/Resolver/FullyQualifiedNameResolver.php +++ b/src/PhpParser/NodeVisitor/Resolver/FullyQualifiedNameResolver.php @@ -14,12 +14,14 @@ namespace Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver; -use Humbug\PhpScoper\PhpParser\NodeVisitor\AppendParentNode; use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\NamespaceStmtCollection; use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UseStmtCollection; use Humbug\PhpScoper\PhpParser\NodeVisitor\NameStmtPrefixer; +use Humbug\PhpScoper\PhpParser\NodeVisitor\ParentNodeAppender; +use PhpParser\Node; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use function in_array; @@ -41,12 +43,22 @@ public function __construct(NamespaceStmtCollection $namespaceStatements, UseStm $this->useStatements = $useStatements; } - public function resolveName(Name $node): ResolvedValue + /** + * @param Name|Identifier + */ + public function resolveName(Node $node): ResolvedValue { if ($node instanceof FullyQualified) { return new ResolvedValue($node, null, null); } + if ($node instanceof Identifier) { + $attributes = $node->getAttributes(); + $attributes['original_node'] = $node; + + $node = new Name($node->name, $attributes); + } + $namespaceName = $this->namespaceStatements->findNamespaceForNode($node); $useName = $this->useStatements->findStatementForNode($namespaceName, $node); @@ -72,7 +84,7 @@ private function resolveNodeName(Name $name, ?Name $namespace, ?Name $use): Name return $name; } - $parentNode = AppendParentNode::getParent($name); + $parentNode = ParentNodeAppender::getParent($name); if ( ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall) diff --git a/src/PhpParser/NodeVisitor/StringScalarPrefixer.php b/src/PhpParser/NodeVisitor/StringScalarPrefixer.php index eb236c6a..ef5a6312 100644 --- a/src/PhpParser/NodeVisitor/StringScalarPrefixer.php +++ b/src/PhpParser/NodeVisitor/StringScalarPrefixer.php @@ -23,24 +23,23 @@ use PhpParser\Node\Expr\ArrayItem; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Param; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\PropertyProperty; use PhpParser\NodeVisitorAbstract; use function array_key_exists; -use function count; +use function array_shift; +use function array_values; use function Humbug\PhpScoper\is_stringable; +use function implode; use function in_array; use function is_string; use function preg_match; use function strpos; /** - * Prefixes the string scalar values. + * Prefixes the string scalar values when appropriate. * * ``` * $x = 'Foo\Bar'; @@ -83,174 +82,211 @@ public function __construct(string $prefix, Whitelist $whitelist, Reflector $ref */ public function enterNode(Node $node): Node { - $isSpecialFunction = false; - - return ($this->shouldPrefixScalar($node, $isSpecialFunction)) - ? $this->prefixStringScalar($node, $isSpecialFunction) + return $node instanceof String_ + ? $this->prefixStringScalar($node) : $node ; } - private function shouldPrefixScalar(Node $node, bool &$isSpecialFunction): bool + private function prefixStringScalar(String_ $string): String_ { - if (false === ($node instanceof String_ && AppendParentNode::hasParent($node) && is_string($node->value)) - || 1 !== preg_match('/^((\\\\)?[\p{L}_]+)$|((\\\\)?(?:[\p{L}_]+\\\\+)+[\p{L}_]+)$/u', $node->value) + if (false === (ParentNodeAppender::hasParent($string) && is_string($string->value)) + || 1 !== preg_match('/^((\\\\)?[\p{L}_]+)$|((\\\\)?(?:[\p{L}_]+\\\\+)+[\p{L}_]+)$/u', $string->value) ) { - return false; + return $string; } - /** @var String_ $node */ - $parentNode = AppendParentNode::getParent($node); + if ($this->whitelist->belongsToWhitelistedNamespace($string->value)) { + return $string; + } + + // From this point either the symbol belongs to the global namespace or the symbol belongs to the symbol + // namespace is whitelisted + + $parentNode = ParentNodeAppender::getParent($string); // The string scalar either has a class form or a simple string which can either be a symbol from the global // namespace or a completely unrelated string. - if ($parentNode instanceof Arg - && null !== $functionNode = AppendParentNode::findParent($parentNode) - ) { - $functionNode = AppendParentNode::getParent($parentNode); - - if ($functionNode instanceof FuncCall) { - $functionName = is_stringable($functionNode->name) ? (string) $functionNode->name : null; - - if (false === strpos((string) $node->value, '\\') - && null !== $functionName - && in_array($functionName, self::SPECIAL_FUNCTION_NAMES, true) - ) { - $isSpecialFunction = true; - - return - ( - 'function_exists' === $functionName - && false === $this->reflector->isFunctionInternal($node->value) - ) - || ( - 'function_exists' !== $functionName - && false === $this->reflector->isClassInternal($node->value) - && false === $this->whitelist->isClassWhitelisted($node->value) - ) - ; - } - - return null !== $functionName && false === $functionNode->hasAttribute('whitelist_class_alias'); - } + if ($parentNode instanceof Arg) { + return $this->prefixStringArg($string, $parentNode); + } - return $functionNode instanceof MethodCall || $functionNode instanceof StaticCall; + if ($parentNode instanceof ArrayItem) { + return $this->prefixArrayItemString($string, $parentNode); } - if (false === ($parentNode instanceof ArrayItem)) { - return $parentNode instanceof Assign + if (false === ( + $parentNode instanceof Assign || $parentNode instanceof Param || $parentNode instanceof Const_ || $parentNode instanceof PropertyProperty - ; + ) + ) { + return $string; } - // ArrayItem can lead to two results: either the string is used for `spl_autoload_register()`, e.g. - // `spl_autoload_register(['Swift', 'autoload'])` in which case the string `'Swift'` is guaranteed to be class - // name, or something else in which case a string like `'Swift'` can be anything and cannot be prefixed. + // If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular string + return $this->belongsToTheGlobalNamespace($string) + ? $string + : $this->createPrefixedString($string) + ; + } - if (substr_count($node->value, '\\') + 1 > 1) { - return true; + private function prefixStringArg(String_ $string, Arg $parentNode): String_ + { + $functionNode = ParentNodeAppender::getParent($parentNode); + + if (false === ($functionNode instanceof FuncCall)) { + // If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular string + return $this->belongsToTheGlobalNamespace($string) + ? $string + : $this->createPrefixedString($string) + ; } + /** @var FuncCall $functionNode */ - $arrayItemNode = $parentNode; + // In the case of a function call, we allow to prefix strings which could be classes belonging to the global + // namespace in some cases + $functionName = is_stringable($functionNode->name) ? (string) $functionNode->name : null; - if (false === AppendParentNode::hasParent($parentNode)) { - return false; + if (false === in_array($functionName, self::SPECIAL_FUNCTION_NAMES, true)) { + return $this->belongsToTheGlobalNamespace($string) + ? $string + : $this->createPrefixedString($string) + ; + } + + if ('function_exists' === $functionName) { + return $this->reflector->isFunctionInternal($string->value) + ? $string + : $this->createPrefixedString($string) + ; } - $parentNode = AppendParentNode::getParent($parentNode); + $isConstantNode = $this->isConstantNode($string); - if (false === ($parentNode instanceof Array_) || false === AppendParentNode::hasParent($parentNode)) { - return false; + if (false === $isConstantNode) { + if ('define' === $functionName + && $this->belongsToTheGlobalNamespace($string) + ) { + return $string; + } + + return $this->reflector->isClassInternal($string->value) + ? $string + : $this->createPrefixedString($string) + ; } + return ($this->whitelist->isSymbolWhitelisted($string->value, true) + || $this->whitelist->isGlobalWhitelistedConstant($string->value) + ) + ? $string + : $this->createPrefixedString($string) + ; + } + + private function prefixArrayItemString(String_ $string, ArrayItem $parentNode): String_ + { + // ArrayItem can lead to two results: either the string is used for `spl_autoload_register()`, e.g. + // `spl_autoload_register(['Swift', 'autoload'])` in which case the string `'Swift'` is guaranteed to be class + // name, or something else in which case a string like `'Swift'` can be anything and cannot be prefixed. + + $arrayItemNode = $parentNode; + + $parentNode = ParentNodeAppender::getParent($parentNode); + /** @var Array_ $arrayNode */ $arrayNode = $parentNode; - $parentNode = AppendParentNode::getParent($parentNode); + $parentNode = ParentNodeAppender::getParent($parentNode); if (false === ($parentNode instanceof Arg) - || null === $functionNode = AppendParentNode::findParent($parentNode) + || null === $functionNode = ParentNodeAppender::findParent($parentNode) ) { - return false; + // If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular string + return $this->belongsToTheGlobalNamespace($string) + ? $string + : $this->createPrefixedString($string) + ; } - $functionNode = AppendParentNode::getParent($parentNode); + $functionNode = ParentNodeAppender::getParent($parentNode); if (false === ($functionNode instanceof FuncCall)) { - return false; + // If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular string + return $this->belongsToTheGlobalNamespace($string) + ? $string + : $this->createPrefixedString($string) + ; } /** @var FuncCall $functionNode */ - if (is_stringable($functionNode->name)) { - $functionName = (string) $functionNode->name; - } else { - return false; - } - - if ('spl_autoload_register' === $functionName - && array_key_exists(0, $arrayNode->items) - && $arrayItemNode === $arrayNode->items[0] - ) { - $isSpecialFunction = true; - - return - false === $this->whitelist->isClassWhitelisted($node->value) - && false === $this->reflector->isClassInternal($node->value) - ; + if (false === is_stringable($functionNode->name)) { + return $string; } - return false; - } - - private function prefixStringScalar(String_ $string, bool $isSpecialFunction): Node - { - $stringName = new Name( - preg_replace('/^\\\\(.+)$/', '$1', $string->value), - $string->getAttributes() - ); - - $isConstantNode = $this->isConstantNode($string); + $functionName = (string) $functionNode->name; - // Skip if is already prefixed - if ($this->prefix === $stringName->getFirst()) { - $newStringName = $stringName; - } elseif ($isSpecialFunction && false === $isConstantNode) { - $newStringName = FullyQualified::concat($this->prefix, $stringName->toString(), $stringName->getAttributes()); - // Check if the class can be prefixed: class not from the global namespace or which the namespace is not - // whitelisted - } elseif ( - 1 === count($stringName->parts) - || $this->reflector->isClassInternal($stringName->toString()) - || (false === $isConstantNode && $this->whitelist->isClassWhitelisted((string) $stringName)) - || ($isConstantNode && $this->whitelist->isConstantWhitelisted((string) $stringName)) - || $this->whitelist->isNamespaceWhitelisted((string) $stringName) - ) { - $newStringName = $stringName; - } else { - $newStringName = FullyQualified::concat($this->prefix, $stringName->toString(), $stringName->getAttributes()); - } - - return new String_($newStringName->toString(), $string->getAttributes()); + return ('spl_autoload_register' === $functionName + && array_key_exists(0, $arrayNode->items) + && $arrayItemNode === $arrayNode->items[0] + && false === $this->reflector->isClassInternal($string->value) + ) + ? $this->createPrefixedString($string) + : $string + ; } private function isConstantNode(String_ $node): bool { - $parent = AppendParentNode::getParent($node); + $parent = ParentNodeAppender::getParent($node); if (false === ($parent instanceof Arg)) { return false; } /** @var Arg $parent */ - $argParent = AppendParentNode::getParent($parent); + $argParent = ParentNodeAppender::getParent($parent); if (false === ($argParent instanceof FuncCall)) { return false; } /* @var FuncCall $argParent */ - return 'define' === (string) $argParent->name; + if ('define' !== (string) $argParent->name) { + return false; + } + + return $parent === $argParent->args[0]; + } + + private function createPrefixedString(String_ $previous): String_ + { + $previousValueParts = array_values( + array_filter( + explode('\\', $previous->value) + ) + ); + + if ($this->prefix === $previousValueParts[0]) { + array_shift($previousValueParts); + } + + $previousValue = implode('\\', $previousValueParts); + + $string = new String_( + (string) FullyQualified::concat($this->prefix, $previousValue), + $previous->getAttributes() + ); + + $string->setAttribute(ParentNodeAppender::PARENT_ATTRIBUTE, $string); + + return $string; + } + + private function belongsToTheGlobalNamespace(String_ $string): bool + { + return '' === $string->value || 0 === (int) strpos($string->value, '\\', 1); } } diff --git a/src/PhpParser/NodeVisitor/UseStmt/UseStmtPrefixer.php b/src/PhpParser/NodeVisitor/UseStmt/UseStmtPrefixer.php index 36fe5a19..84df5977 100644 --- a/src/PhpParser/NodeVisitor/UseStmt/UseStmtPrefixer.php +++ b/src/PhpParser/NodeVisitor/UseStmt/UseStmtPrefixer.php @@ -14,7 +14,7 @@ namespace Humbug\PhpScoper\PhpParser\NodeVisitor\UseStmt; -use Humbug\PhpScoper\PhpParser\NodeVisitor\AppendParentNode; +use Humbug\PhpScoper\PhpParser\NodeVisitor\ParentNodeAppender; use Humbug\PhpScoper\Reflector; use Humbug\PhpScoper\Whitelist; use PhpParser\Node; @@ -63,7 +63,7 @@ private function shouldPrefixUseStmt(UseUse $use): bool } // If is whitelisted - if ($this->whitelist->isNamespaceWhitelisted((string) $use->name)) { + if ($this->whitelist->belongsToWhitelistedNamespace((string) $use->name)) { return false; } @@ -73,7 +73,7 @@ private function shouldPrefixUseStmt(UseUse $use): bool if (Use_::TYPE_CONSTANT === $useType) { return - false === $this->whitelist->isClassWhitelisted((string) $use->name) + false === $this->whitelist->isSymbolWhitelisted((string) $use->name) && false === $this->reflector->isConstantInternal((string) $use->name) ; } @@ -92,7 +92,7 @@ private function findUseType(UseUse $use): int { if (Use_::TYPE_UNKNOWN === $use->type) { /** @var Use_ $parentNode */ - $parentNode = AppendParentNode::getParent($use); + $parentNode = ParentNodeAppender::getParent($use); return $parentNode->type; } diff --git a/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php b/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php deleted file mode 100644 index 0ccedd23..00000000 --- a/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php +++ /dev/null @@ -1,124 +0,0 @@ -, - * Pádraic Brady - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Humbug\PhpScoper\PhpParser\NodeVisitor; - -use Humbug\PhpScoper\Whitelist; -use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr\ConstFetch; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\Expression; -use PhpParser\Node\Stmt\Interface_; -use PhpParser\Node\Stmt\Namespace_; -use PhpParser\NodeVisitorAbstract; - -/** - * Appends a `class_alias` to the whitelisted classes. - * - * ``` - * namespace A; - * - * class Foo - * { - * } - * ``` - * - * => - * - * ``` - * namespace Humbug\A; - * - * class Foo - * { - * } - * - * class_alias('Humbug\A\Foo', 'A\Foo', false); - * ``` - * - * @internal - */ -final class WhitelistedClassAppender extends NodeVisitorAbstract -{ - private $whitelist; - - public function __construct(Whitelist $whitelist) - { - $this->whitelist = $whitelist; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - return ($node instanceof Namespace_) - ? $this->appendToNamespaceStmt($node) - : $node; - } - - private function appendToNamespaceStmt(Namespace_ $namespace): Namespace_ - { - if (false === $this->whitelist->hasWhitelistStatements()) { - return $namespace; - } - - $newStmts = []; - - foreach ($namespace->stmts as $stmt) { - $newStmts[] = $stmt; - - if (false === ($stmt instanceof Class_ || $stmt instanceof Interface_)) { - continue; - } - - /** @var Class_ $stmt */ - $name = null === $namespace->name - ? new FullyQualified((string) $stmt->name, $stmt->getAttributes()) - : FullyQualified::concat((string) $namespace->name, (string) $stmt->name) - ; - $originalName = $name->slice(1); - - if (false === $this->whitelist->isClassWhitelisted((string) $originalName)) { - continue; - } - - $newStmts[] = new Expression(new FuncCall( - new Name('class_alias'), - [ - new Arg( - new String_((string) $name) - ), - new Arg( - new String_((string) $originalName) - ), - new Arg( - new ConstFetch( - new FullyQualified('false') - ) - ), - ], - ['whitelist_class_alias' => true] - )); - } - - $namespace->stmts = $newStmts; - - return $namespace; - } -} diff --git a/src/PhpParser/TraverserFactory.php b/src/PhpParser/TraverserFactory.php index a76e6e8c..5a557af7 100644 --- a/src/PhpParser/TraverserFactory.php +++ b/src/PhpParser/TraverserFactory.php @@ -42,7 +42,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter $nameResolver = new FullyQualifiedNameResolver($namespaceStatements, $useStatements); - $traverser->addVisitor(new NodeVisitor\AppendParentNode()); + $traverser->addVisitor(new NodeVisitor\ParentNodeAppender()); $traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $whitelist, $namespaceStatements)); @@ -53,7 +53,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter $traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector)); $traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $this->reflector)); - $traverser->addVisitor(new NodeVisitor\WhitelistedClassAppender($whitelist)); + $traverser->addVisitor(new NodeVisitor\ClassAliasStmtAppender($prefix, $whitelist, $nameResolver)); $traverser->addVisitor(new NodeVisitor\ConstStmtReplacer($whitelist, $nameResolver)); return $traverser; diff --git a/src/Scoper/Composer/AutoloadPrefixer.php b/src/Scoper/Composer/AutoloadPrefixer.php index 9ffab27a..2fe3026f 100644 --- a/src/Scoper/Composer/AutoloadPrefixer.php +++ b/src/Scoper/Composer/AutoloadPrefixer.php @@ -67,7 +67,7 @@ private static function prefixAutoload(array $autoload, string $prefix, Whitelis $loader = []; foreach ($autoload as $namespace => $paths) { - $newNamespace = $whitelist->isNamespaceWhitelisted($namespace) + $newNamespace = $whitelist->belongsToWhitelistedNamespace($namespace) ? $namespace : sprintf('%s\\%s', $prefix, $namespace) ; diff --git a/src/Whitelist.php b/src/Whitelist.php index 8d46053d..a01a2615 100644 --- a/src/Whitelist.php +++ b/src/Whitelist.php @@ -14,15 +14,16 @@ namespace Humbug\PhpScoper; -use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\WhitelistedFunctionCollection; +use Countable; use InvalidArgumentException; use PhpParser\Node\Name\FullyQualified; -use function array_filter; +use const SORT_REGULAR; use function array_flip; use function array_key_exists; use function array_map; use function array_pop; use function array_unique; +use function array_values; use function count; use function explode; use function implode; @@ -34,20 +35,23 @@ use function substr; use function trim; -final class Whitelist +final class Whitelist implements Countable { private $original; - private $classes; + private $symbols; private $constants; private $namespaces; private $patterns; + private $whitelistGlobalConstants; private $whitelistGlobalFunctions; - private $whitelistedFunctions; + + private $whitelistedFunctions = []; + private $whitelistedClasses = []; public static function create(bool $whitelistGlobalConstants, bool $whitelistGlobalFunctions, string ...$elements): self { - $classes = []; + $symbols = []; $constants = []; $namespaces = []; $patterns = []; @@ -77,21 +81,19 @@ public static function create(bool $whitelistGlobalConstants, bool $whitelistGlo self::assertValidPattern($element); $patterns[] = sprintf( - '/^%s$/ui', - strtolower( + '/^%s$/u', + str_replace( + '\\', + '\\\\', str_replace( - '\\', - '\\\\', - str_replace( - '*', - '.*', - $element - ) + '*', + '.*', + $element ) ) ); } else { - $classes[] = strtolower($element); + $symbols[] = strtolower($element); $constants[] = self::lowerConstantName($element); } } @@ -100,7 +102,7 @@ public static function create(bool $whitelistGlobalConstants, bool $whitelistGlo $whitelistGlobalConstants, $whitelistGlobalFunctions, array_unique($original), - array_flip($classes), + array_flip($symbols), array_flip($constants), array_unique($patterns), array_unique($namespaces) @@ -128,7 +130,7 @@ private function __construct( bool $whitelistGlobalConstants, bool $whitelistGlobalFunctions, array $original, - array $classes, + array $symbols, array $constants, array $patterns, array $namespaces @@ -136,55 +138,115 @@ private function __construct( $this->whitelistGlobalConstants = $whitelistGlobalConstants; $this->whitelistGlobalFunctions = $whitelistGlobalFunctions; $this->original = $original; - $this->classes = $classes; + $this->symbols = $symbols; $this->constants = $constants; $this->namespaces = $namespaces; $this->patterns = $patterns; - $this->whitelistedFunctions = new WhitelistedFunctionCollection(); + } + + public function belongsToWhitelistedNamespace(string $name): bool + { + $name = strtolower($name); + + if (0 === strpos($name, '\\')) { + $name = substr($name, 1); + } + + foreach ($this->namespaces as $namespace) { + if ('' === $namespace || 0 === strpos($name, $namespace)) { + return true; + } + } + + return false; + } + + /** + * @internal + */ + public function whitelistGlobalFunctions(): bool + { + return $this->whitelistGlobalFunctions; + } + + public function isGlobalWhitelistedFunction(string $functionName): bool + { + return $this->whitelistGlobalFunctions && false === strpos($functionName, '\\'); } public function recordWhitelistedFunction(FullyQualified $original, FullyQualified $alias): void { - $this->whitelistedFunctions->add($original, $alias); + $this->whitelistedFunctions[] = [(string) $original, (string) $alias]; } - public function getWhitelistedFunctions(): WhitelistedFunctionCollection + public function getWhitelistedFunctions(): array { - return $this->whitelistedFunctions; + return array_values( + array_unique( + $this->whitelistedFunctions, + SORT_REGULAR + ) + ); } + /** + * @internal + */ public function whitelistGlobalConstants(): bool { return $this->whitelistGlobalConstants; } - public function whitelistGlobalFunctions(): bool + public function isGlobalWhitelistedConstant(string $constantName): bool + { + return $this->whitelistGlobalConstants && false === strpos($constantName, '\\'); + } + + /** + * @internal + */ + public function whitelistGlobalClasses(): bool { return $this->whitelistGlobalFunctions; } - public function isClassWhitelisted(string $name): bool + public function isGlobalWhitelistedClass(string $className): bool { - if (array_key_exists(strtolower($name), $this->classes)) { - return true; - } + return false; + } - foreach ($this->patterns as $pattern) { - if (1 === preg_match($pattern, $name)) { - return true; - } - } + public function recordWhitelistedClass(FullyQualified $original, FullyQualified $alias): void + { + $this->whitelistedClasses[] = [(string) $original, (string) $alias]; + } - return false; + public function getWhitelistedClasses(): array + { + return $this->whitelistedClasses; } - public function isConstantWhitelisted(string $name): bool + /** + * Tells if a given symbol is whitelisted. Note however that it does not account for when:. + * + * - The symbol belongs to the global namespace and the symbols of the global namespace of this type are whitelisted + * - Belongs to a whitelisted namespace + * + * @param bool $constant Unlike other symbols, constants _can_ be case insensitive but 99% are not so we leave out + * the case where they are not case sensitive. + */ + public function isSymbolWhitelisted(string $name, bool $constant = false): bool { - if (array_key_exists(self::lowerConstantName($name), $this->constants)) { + if (false === $constant && array_key_exists(strtolower($name), $this->symbols)) { + return true; + } + + if ($constant && array_key_exists(self::lowerConstantName($name), $this->constants)) { return true; } foreach ($this->patterns as $pattern) { + $pattern = false === $constant ? $pattern.'i' : $pattern; + if (1 === preg_match($pattern, $name)) { return true; } @@ -195,6 +257,8 @@ public function isConstantWhitelisted(string $name): bool /** * @return string[] + * + * @deprecated To be replaced by getWhitelistedClasses */ public function getClassWhitelistArray(): array { @@ -206,29 +270,23 @@ function (string $name): bool { ); } - public function isNamespaceWhitelisted(string $name): bool - { - $name = strtolower($name); - - foreach ($this->namespaces as $namespace) { - if ('' === $namespace || 0 === strpos($name, $namespace)) { - return true; - } - } - - return false; - } - public function toArray(): array { return $this->original; } - public function hasWhitelistStatements(): bool + /** + * {@inheritdoc} + */ + public function count(): int { - return count($this->classes) + count($this->whitelistedFunctions) + count($this->patterns) > 0; + return count($this->whitelistedFunctions) + count($this->whitelistedClasses); } + /** + * Transforms the constant FQ name "Acme\Foo\X" to "acme\foo\X" since the namespace remains case insensitive for + * constants regardless of whether or not constants actually are case insensitive. + */ private static function lowerConstantName(string $name): string { $parts = explode('\\', $name); diff --git a/tests/Scoper/PhpScoperTest.php b/tests/Scoper/PhpScoperTest.php index b8d9d0b0..495e4557 100644 --- a/tests/Scoper/PhpScoperTest.php +++ b/tests/Scoper/PhpScoperTest.php @@ -471,6 +471,7 @@ public function test_can_scope_valid_files(string $spec, string $contents, strin ; $formattedWhitelistGlobalConstants = $whitelist->whitelistGlobalConstants() ? 'true' : 'false'; + $formattedWhitelistGlobalFunctions = $whitelist->whitelistGlobalFunctions() ? 'true' : 'false'; $titleSeparator = str_repeat( '=', @@ -493,6 +494,7 @@ public function test_can_scope_valid_files(string $spec, string $contents, strin INPUT whitelist: $formattedWhitelist whitelist global constants: $formattedWhitelistGlobalConstants +whitelist global functions: $formattedWhitelistGlobalFunctions $titleSeparator $contents diff --git a/tests/WhitelistTest.php b/tests/WhitelistTest.php index 4ff6a8e0..1249c082 100644 --- a/tests/WhitelistTest.php +++ b/tests/WhitelistTest.php @@ -14,6 +14,8 @@ namespace Humbug\PhpScoper; +use Generator; +use PhpParser\Node\Name\FullyQualified; use PHPUnit\Framework\TestCase; use ReflectionClass; use function array_flip; @@ -29,14 +31,16 @@ class WhitelistTest extends TestCase public function test_it_can_be_created_from_a_list_of_strings( array $whitelist, array $expectedNamespaces, - array $expectedClasses, + array $expectedSymbols, array $expectedConstants ) { $whitelistObject = Whitelist::create(true, true, ...$whitelist); $whitelistReflection = new ReflectionClass(Whitelist::class); - $actualClasses = $whitelistObject->getClassWhitelistArray(); + $whitelistSymbolReflection = $whitelistReflection->getProperty('symbols'); + $whitelistSymbolReflection->setAccessible(true); + $actualSymbols = $whitelistSymbolReflection->getValue($whitelistObject); $whitelistNamespaceReflection = $whitelistReflection->getProperty('namespaces'); $whitelistNamespaceReflection->setAccessible(true); @@ -47,24 +51,110 @@ public function test_it_can_be_created_from_a_list_of_strings( $actualConstants = $whitelistConstantReflection->getValue($whitelistObject); $this->assertTrue($whitelistObject->whitelistGlobalConstants()); + $this->assertTrue($whitelistObject->whitelistGlobalFunctions()); $this->assertSame($expectedNamespaces, $actualNamespaces); - $this->assertSame($expectedClasses, $actualClasses); - $this->assertSame(array_flip($expectedConstants), $actualConstants); + $this->assertSame($expectedSymbols, array_flip($actualSymbols)); + $this->assertSame($expectedConstants, array_flip($actualConstants)); $whitelistObject = Whitelist::create(false, false, ...$whitelist); $this->assertFalse($whitelistObject->whitelistGlobalConstants()); - $this->assertSame($expectedClasses, $actualClasses); + $this->assertFalse($whitelistObject->whitelistGlobalFunctions()); $this->assertSame($expectedNamespaces, $actualNamespaces); - $this->assertSame(array_flip($expectedConstants), $actualConstants); + $this->assertSame($expectedSymbols, array_flip($actualSymbols)); + $this->assertSame($expectedConstants, array_flip($actualConstants)); } /** - * @dataProvider provideClassWhitelists + * @dataProvider provideGlobalConstantNames */ - public function test_it_can_tell_if_a_class_is_whitelisted(Whitelist $whitelist, string $class, bool $expected) + public function test_it_can_tell_if_a_constant_is_a_whitelisted_global_constant(Whitelist $whitelist, string $constant, bool $expected) { - $actual = $whitelist->isClassWhitelisted($class); + $actual = $whitelist->isGlobalWhitelistedConstant($constant); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider provideGlobalClassNames + */ + public function test_it_can_tell_if_a_class_is_a_whitelisted_global_class(Whitelist $whitelist, string $constant, bool $expected) + { + $this->markTestSkipped('TODO'); + $actual = $whitelist->isGlobalWhitelistedClass($constant); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider provideGlobalFunctionNames + */ + public function test_it_can_tell_if_a_function_is_a_whitelisted_global_function(Whitelist $whitelist, string $constant, bool $expected) + { + $actual = $whitelist->isGlobalWhitelistedFunction($constant); + + $this->assertSame($expected, $actual); + } + + public function test_it_can_record_whitelisted_functions() + { + $whitelist = Whitelist::create(true, true); + + $whitelist->recordWhitelistedFunction( + new FullyQualified('Acme\foo'), + new FullyQualified('Humbug\Acme\foo') + ); + $whitelist->recordWhitelistedFunction( + new FullyQualified('Acme\foo'), + new FullyQualified('Humbug\Acme\foo') + ); + $whitelist->recordWhitelistedFunction( + new FullyQualified('Acme\bar'), + new FullyQualified('Humbug\Acme\bar') + ); + + $this->assertSame( + [ + ['Acme\foo', 'Humbug\Acme\foo'], + ['Acme\bar', 'Humbug\Acme\bar'], + ], + $whitelist->getWhitelistedFunctions() + ); + } + + public function test_it_can_record_whitelisted_classes() + { + $this->markTestSkipped('TODO'); + $whitelist = Whitelist::create(true, true); + + $whitelist->recordWhitelistedClass( + new FullyQualified('Acme\foo'), + new FullyQualified('Humbug\Acme\foo') + ); + $whitelist->recordWhitelistedClass( + new FullyQualified('Acme\foo'), + new FullyQualified('Humbug\Acme\foo') + ); + $whitelist->recordWhitelistedClass( + new FullyQualified('Acme\bar'), + new FullyQualified('Humbug\Acme\bar') + ); + + $this->assertSame( + [ + ['Acme\foo', 'Humbug\Acme\foo'], + ['Acme\bar', 'Humbug\Acme\bar'], + ], + $whitelist->getWhitelistedFunctions() + ); + } + + /** + * @dataProvider provideSymbolNames + */ + public function test_it_can_tell_if_a_symbol_is_whitelisted(Whitelist $whitelist, string $symbol, bool $caseSensitive, bool $expected) + { + $actual = $whitelist->isSymbolWhitelisted($symbol, $caseSensitive); $this->assertSame($expected, $actual); } @@ -74,7 +164,7 @@ public function test_it_can_tell_if_a_class_is_whitelisted(Whitelist $whitelist, */ public function test_it_can_tell_if_a_namespace_is_whitelisted(Whitelist $whitelist, string $class, bool $expected) { - $actual = $whitelist->isNamespaceWhitelisted($class); + $actual = $whitelist->belongsToWhitelistedNamespace($class); $this->assertSame($expected, $actual); } @@ -89,11 +179,11 @@ public function test_it_can_be_converted_back_into_an_array(Whitelist $whitelist $this->assertSame($expected, $actual); } - public function provideWhitelists() + public function provideWhitelists(): Generator { yield [[], [], [], []]; - yield [['Acme\Foo'], [], ['Acme\Foo'], ['acme\Foo']]; + yield [['Acme\Foo'], [], ['acme\foo'], ['acme\Foo']]; yield [['Acme\Foo\*'], ['acme\foo'], [], []]; @@ -104,34 +194,127 @@ public function provideWhitelists() yield [ ['Acme\Foo', 'Acme\Foo\*', '\*'], ['acme\foo', ''], - ['Acme\Foo'], + ['acme\foo'], ['acme\Foo'], ]; } - public function provideClassWhitelists() + public function provideGlobalConstantNames(): Generator + { + yield [ + Whitelist::create(true, true), + 'PHP_SCOPER_VERSION', + true, + ]; + + yield [ + Whitelist::create(false, true), + 'PHP_SCOPER_VERSION', + false, + ]; + + yield [ + Whitelist::create(true, true), + 'Humbug\PHP_SCOPER_VERSION', + false, + ]; + + yield [ + Whitelist::create(false, true), + 'Humbug\PHP_SCOPER_VERSION', + false, + ]; + + yield [ + Whitelist::create(true, true), + 'Humbug\PHP_SCOPER_VERSION', + false, + ]; + + yield [ + Whitelist::create(false, true, 'PHP_SCOPER_VERSION'), + 'PHP_SCOPER_VERSION', + false, + ]; + } + + public function provideGlobalClassNames(): Generator + { + yield [ + Whitelist::create(true, true, 'PHP_SCOPER_VERSION'), + 'Foo', + false, + ]; + } + + public function provideGlobalFunctionNames(): Generator + { + yield [ + Whitelist::create(true, true, 'PHP_SCOPER_VERSION'), + 'foo', + true, + ]; + + yield [ + Whitelist::create(true, false, 'PHP_SCOPER_VERSION'), + 'foo', + false, + ]; + + yield [ + Whitelist::create(true, true, 'PHP_SCOPER_VERSION'), + 'Acme\foo', + false, + ]; + + yield [ + Whitelist::create(true, false, 'PHP_SCOPER_VERSION'), + 'Acme\foo', + false, + ]; + } + + public function provideSymbolNames(): Generator { yield [ Whitelist::create(true, true), 'Acme\Foo', false, + false, ]; yield [ Whitelist::create(true, true, 'Acme\Foo'), 'Acme\Foo', + false, + true, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\Foo'), + 'acme\foo', + false, true, ]; + yield [ + Whitelist::create(true, true, 'Acme\Foo'), + 'acme\foo', + true, + false, + ]; + yield [ Whitelist::create(true, true, 'Acme\Foo'), 'Acme\Foo\Bar', + true, false, ]; yield [ Whitelist::create(true, true, 'Acme\Foo'), 'Acme', + true, false, ]; @@ -139,16 +322,144 @@ public function provideClassWhitelists() Whitelist::create(true, true, 'Acme'), 'Acme', true, + true, + ]; + + yield [ + Whitelist::create(true, true, 'Acme'), + 'Acme', + false, + true, + ]; + + yield [ + Whitelist::create(true, true, 'Acme'), + 'acme', + false, + true, + ]; + + yield [ + Whitelist::create(true, true, 'Acme'), + 'acme', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'Acme', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'Acme', + false, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'acme', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'acme', + false, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'Acme\Foo', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'Acme\Foo', + false, + false, ]; yield [ Whitelist::create(true, true, 'Acme\*'), + 'acme\Foo', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\*'), + 'acme\Foo', + false, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), 'Acme', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'Acme', + false, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'acme', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'acme', + false, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'Acme\Foo', + true, + true, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'Acme\Foo', false, + true, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'acme\foo', + true, + false, + ]; + + yield [ + Whitelist::create(true, true, 'Acme\F*'), + 'acme\foo', + false, + true, ]; } - public function provideNamespaceWhitelists() + public function provideNamespaceWhitelists(): Generator { yield [ Whitelist::create(true, true), @@ -217,7 +528,7 @@ public function provideNamespaceWhitelists() ]; } - public function provideWhitelistToConvert() + public function provideWhitelistToConvert(): Generator { yield [ Whitelist::create(true, true),