From 6f70bd31f921044c02a595373f6d39f3ea29474f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Mon, 4 Jun 2018 23:18:03 +0100 Subject: [PATCH 1/9] Created a whitelist class --- _specs/.gitignore | 1 - specs/class/abstract.php | 30 +++++- specs/func-declaration/namespace.php | 91 ++++++++++++++++++- src/Configuration.php | 12 +-- src/Console/Command/AddPrefixCommand.php | 15 ++- .../NodeVisitor/WhitelistedClassAppender.php | 10 +- src/PhpParser/TraverserFactory.php | 3 +- src/Scoper.php | 4 +- .../Composer/InstalledPackagesScoper.php | 3 +- src/Scoper/Composer/JsonFileScoper.php | 3 +- src/Scoper/NullScoper.php | 3 +- src/Scoper/PatchScoper.php | 3 +- src/Scoper/PhpScoper.php | 3 +- src/Whitelist.php | 74 +++++++++++++++ .../Console/Command/AddPrefixCommandTest.php | 27 +++--- tests/PhpParser/TraverserFactoryTest.php | 3 +- .../Composer/InstalledPackagesScoperTest.php | 5 +- tests/Scoper/Composer/JsonFileScoperTest.php | 7 +- tests/Scoper/FakeScoper.php | 3 +- tests/Scoper/NullScoperTest.php | 3 +- tests/Scoper/PatchScoperTest.php | 3 +- tests/Scoper/PhpScoperTest.php | 19 ++-- 22 files changed, 262 insertions(+), 63 deletions(-) delete mode 100644 _specs/.gitignore create mode 100644 src/Whitelist.php diff --git a/_specs/.gitignore b/_specs/.gitignore deleted file mode 100644 index 72e8ffc0..00000000 --- a/_specs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/specs/class/abstract.php b/specs/class/abstract.php index beeab07f..aa8eb9cb 100644 --- a/specs/class/abstract.php +++ b/specs/class/abstract.php @@ -94,7 +94,35 @@ public abstract function b(); \class_alias('Humbug\\Foo\\A', 'Foo\\A', \false); PHP - ], + ], + + 'Declaration of a whitelisted class with FQCN for the whitelist: append aliasing.' => [ + 'whitelist' => ['\Foo\A'], + 'payload' => <<<'PHP' + <<<'SPEC' diff --git a/specs/func-declaration/namespace.php b/specs/func-declaration/namespace.php index dc896e18..85ae7ce9 100644 --- a/specs/func-declaration/namespace.php +++ b/specs/func-declaration/namespace.php @@ -110,7 +110,96 @@ function foo(\Humbug\Pi\Foo $arg0 = null, \Humbug\Foo $arg1, \Humbug\Pi\Foo\Bar PHP ], - //TODO: there is an issue here with the whitelisted class + [ + 'spec' => <<<'SPEC' +Function declaration in a namespace: +- prefix the namespace statements +- prefix the appropriate classes +SPEC + , + 'whitelist' => ['X\Y'], + 'payload' => <<<'PHP' + <<<'SPEC' Function declaration in a namespace with use statements: diff --git a/src/Configuration.php b/src/Configuration.php index b18471a6..10e9495d 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -88,7 +88,7 @@ public static function load(string $path = null, array $paths = []): self * @param [string, string][] $filesWithContents Array of tuple with the first argument being the file path and the second its contents * @param callable[] $patchers List of closures which can alter the content of the files being * scoped. - * @param string[] $whitelist List of classes that will not be scoped. + * @param Whitelist $whitelist List of classes that will not be scoped. * @param Closure $globalNamespaceWhitelisters Closure taking a class name from the global namespace as an argument and * returning a boolean which if `true` means the class should be scoped * (i.e. is ignored) or scoped otherwise. @@ -98,7 +98,7 @@ private function __construct( ?string $prefix, array $filesWithContents, array $patchers, - array $whitelist + Whitelist $whitelist ) { $this->path = $path; $this->prefix = $prefix; @@ -162,7 +162,7 @@ public function getPatchers(): array return $this->patchers; } - public function getWhitelist(): array + public function getWhitelist(): Whitelist { return $this->whitelist; } @@ -239,10 +239,10 @@ private static function retrievePatchers(array $config): array return $patchers; } - private static function retrieveWhitelist(array $config): array + private static function retrieveWhitelist(array $config): Whitelist { if (false === array_key_exists(self::WHITELIST_KEYWORD, $config)) { - return []; + return Whitelist::create(); } $whitelist = $config[self::WHITELIST_KEYWORD]; @@ -269,7 +269,7 @@ private static function retrieveWhitelist(array $config): array ); } - return $whitelist; + return Whitelist::create(...$whitelist); } private static function retrieveFinders(array $config): array diff --git a/src/Console/Command/AddPrefixCommand.php b/src/Console/Command/AddPrefixCommand.php index e9556b89..3b38ee7f 100644 --- a/src/Console/Command/AddPrefixCommand.php +++ b/src/Console/Command/AddPrefixCommand.php @@ -19,6 +19,7 @@ use Humbug\PhpScoper\Logger\ConsoleLogger; use Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Throwable\Exception\ParsingException; +use Humbug\PhpScoper\Whitelist; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -165,12 +166,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @var callable[] $patchers + */ private function scopeFiles( string $prefix, array $filesWithContents, string $output, array $patchers, - array $whitelist, + Whitelist $whitelist, bool $stopOnFailure, ConsoleLogger $logger ): void { @@ -220,14 +224,7 @@ function ($a, $b) { } /** - * @param string $inputFilePath - * @param string $outputFilePath - * @param string $inputContents - * @param string $prefix * @param callable[] $patchers - * @param string[] $whitelist - * @param bool $stopOnFailure - * @param ConsoleLogger $logger */ private function scopeFile( string $inputFilePath, @@ -235,7 +232,7 @@ private function scopeFile( string $outputFilePath, string $prefix, array $patchers, - array $whitelist, + Whitelist $whitelist, bool $stopOnFailure, ConsoleLogger $logger ): void { diff --git a/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php b/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php index 84821c66..49c0db02 100644 --- a/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php +++ b/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php @@ -14,6 +14,7 @@ namespace Humbug\PhpScoper\PhpParser\NodeVisitor; +use Humbug\PhpScoper\Whitelist; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr\ConstFetch; @@ -56,12 +57,9 @@ final class WhitelistedClassAppender extends NodeVisitorAbstract { private $whitelist; - /** - * @param string[] $whitelists - */ - public function __construct(array $whitelists) + public function __construct(Whitelist $whitelist) { - $this->whitelist = $whitelists; + $this->whitelist = $whitelist; } /** @@ -93,7 +91,7 @@ private function appendToNamespaceStmt(Namespace_ $namespace): Namespace_ $name = FullyQualified::concat((string) $namespace->name, (string) $stmt->name); $originalName = $name->slice(1); - if (false === in_array((string) $originalName, $this->whitelist, true)) { + if (false === $this->whitelist->isClassWhitelisted((string) $originalName)) { continue; } diff --git a/src/PhpParser/TraverserFactory.php b/src/PhpParser/TraverserFactory.php index 337cd07c..700e723c 100644 --- a/src/PhpParser/TraverserFactory.php +++ b/src/PhpParser/TraverserFactory.php @@ -18,6 +18,7 @@ use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UseStmtCollection; use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver; use Humbug\PhpScoper\Reflector; +use Humbug\PhpScoper\Whitelist; use PhpParser\NodeTraverserInterface; /** @@ -32,7 +33,7 @@ public function __construct(Reflector $reflector) $this->reflector = $reflector; } - public function create(string $prefix, array $whitelist): NodeTraverserInterface + public function create(string $prefix, Whitelist $whitelist): NodeTraverserInterface { $traverser = new NodeTraverser($prefix); diff --git a/src/Scoper.php b/src/Scoper.php index d468e898..1100099a 100644 --- a/src/Scoper.php +++ b/src/Scoper.php @@ -25,11 +25,11 @@ interface Scoper * @param string $contents File contents * @param string $prefix Prefix to apply to the file * @param callable[] $patchers - * @param string[] $whitelist List of classes to exclude from the scoping. + * @param Whitelist $whitelist List of classes to exclude from the scoping. * * @throws ParsingException * * @return string Contents of the file with the prefix applied */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string; + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string; } diff --git a/src/Scoper/Composer/InstalledPackagesScoper.php b/src/Scoper/Composer/InstalledPackagesScoper.php index 4e662a22..51d35d3d 100644 --- a/src/Scoper/Composer/InstalledPackagesScoper.php +++ b/src/Scoper/Composer/InstalledPackagesScoper.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Scoper\Composer; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; final class InstalledPackagesScoper implements Scoper { @@ -32,7 +33,7 @@ public function __construct(Scoper $decoratedScoper) * * {@inheritdoc} */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string { if (1 !== preg_match(self::$filePattern, $filePath)) { return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist); diff --git a/src/Scoper/Composer/JsonFileScoper.php b/src/Scoper/Composer/JsonFileScoper.php index 1e2d9f2c..fd8317bd 100644 --- a/src/Scoper/Composer/JsonFileScoper.php +++ b/src/Scoper/Composer/JsonFileScoper.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Scoper\Composer; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; final class JsonFileScoper implements Scoper { @@ -30,7 +31,7 @@ public function __construct(Scoper $decoratedScoper) * * {@inheritdoc} */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string { if (1 !== preg_match('/composer\.json$/', $filePath)) { return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist); diff --git a/src/Scoper/NullScoper.php b/src/Scoper/NullScoper.php index 306c34cc..3ccfb637 100644 --- a/src/Scoper/NullScoper.php +++ b/src/Scoper/NullScoper.php @@ -15,13 +15,14 @@ namespace Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; final class NullScoper implements Scoper { /** * @inheritdoc */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string { return $contents; } diff --git a/src/Scoper/PatchScoper.php b/src/Scoper/PatchScoper.php index 3a523b51..c05a23ae 100644 --- a/src/Scoper/PatchScoper.php +++ b/src/Scoper/PatchScoper.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; final class PatchScoper implements Scoper { @@ -28,7 +29,7 @@ public function __construct(Scoper $decoratedScoper) /** * @inheritdoc */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string { $contents = $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist); diff --git a/src/Scoper/PhpScoper.php b/src/Scoper/PhpScoper.php index e6923b7c..ce465edc 100644 --- a/src/Scoper/PhpScoper.php +++ b/src/Scoper/PhpScoper.php @@ -16,6 +16,7 @@ use Humbug\PhpScoper\PhpParser\TraverserFactory; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; use PhpParser\Error as PhpParserError; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard; @@ -45,7 +46,7 @@ public function __construct(Parser $parser, Scoper $decoratedScoper, TraverserFa * * @throws PhpParserError */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string { if (false === $this->isPhpFile($filePath, $contents)) { return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist); diff --git a/src/Whitelist.php b/src/Whitelist.php new file mode 100644 index 00000000..c0007ce3 --- /dev/null +++ b/src/Whitelist.php @@ -0,0 +1,74 @@ +classes = $classes; + $this->namespaces = $namespaces; + } + + public function isClassWhitelisted(string $name): bool + { + return in_array($name, $this->classes, true); + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + return count($this->classes) + count($this->namespaces); + } +} \ No newline at end of file diff --git a/tests/Console/Command/AddPrefixCommandTest.php b/tests/Console/Command/AddPrefixCommandTest.php index 469d8cef..2796bc4d 100644 --- a/tests/Console/Command/AddPrefixCommandTest.php +++ b/tests/Console/Command/AddPrefixCommandTest.php @@ -17,6 +17,7 @@ use Humbug\PhpScoper\Console\Application; use Humbug\PhpScoper\FileSystemTestCase; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; use InvalidArgumentException; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -173,7 +174,7 @@ public function test_scope_the_given_paths() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -236,7 +237,7 @@ public function test_let_the_file_unchanged_when_cannot_scope_a_file() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -249,7 +250,7 @@ public function test_let_the_file_unchanged_when_cannot_scope_a_file() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willThrow(new \RuntimeException('Scoping of the file failed')) ; @@ -309,7 +310,7 @@ public function test_do_not_scope_duplicated_given_paths() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -370,7 +371,7 @@ public function test_scope_the_given_paths_and_the_ones_found_by_the_finder() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedFileContents) ; @@ -424,7 +425,7 @@ function (string $prefix): bool { } ), [], - [] + Whitelist::create() ) ->willReturn('') ; @@ -482,7 +483,7 @@ public function test_scope_the_current_working_directory_if_no_path_given() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -528,7 +529,7 @@ public function test_prefix_can_end_by_a_backslash() Argument::any(), 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn('') ; @@ -571,7 +572,7 @@ public function test_prefix_can_end_by_multiple_backslashes() Argument::any(), 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn('') ; @@ -629,7 +630,7 @@ public function test_an_output_directory_can_be_given() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -691,7 +692,7 @@ public function test_relative_output_directory_are_made_absolute() $inputContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -782,7 +783,7 @@ public function test_attempts_to_use_patch_file_in_current_directory() return true; }), - [] + Whitelist::create() ) ->willReturn($prefixedContents) ; @@ -867,7 +868,7 @@ public function test_can_scope_projects_with_invalid_files() $fileContents, 'MyPrefix', [], - [] + Whitelist::create() ) ->willThrow($scopingException = new RuntimeException('Could not scope file')) ; diff --git a/tests/PhpParser/TraverserFactoryTest.php b/tests/PhpParser/TraverserFactoryTest.php index 8595b275..b1de55e2 100644 --- a/tests/PhpParser/TraverserFactoryTest.php +++ b/tests/PhpParser/TraverserFactoryTest.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\PhpParser; use Humbug\PhpScoper\Reflector; +use Humbug\PhpScoper\Whitelist; use PHPUnit\Framework\TestCase; use Roave\BetterReflection\BetterReflection; @@ -27,7 +28,7 @@ public function test_creates_a_new_traverser_at_each_call() { $prefix = 'Humbug'; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $classReflector = new Reflector( (new BetterReflection())->classReflector(), diff --git a/tests/Scoper/Composer/InstalledPackagesScoperTest.php b/tests/Scoper/Composer/InstalledPackagesScoperTest.php index be9dfc8a..58f0ac37 100644 --- a/tests/Scoper/Composer/InstalledPackagesScoperTest.php +++ b/tests/Scoper/Composer/InstalledPackagesScoperTest.php @@ -16,6 +16,7 @@ use Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper\FakeScoper; +use Humbug\PhpScoper\Whitelist; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -38,7 +39,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_insta $fileContents = ''; $prefix = 'Humbug'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); /** @var Scoper|ObjectProphecy $decoratedScoperProphecy */ $decoratedScoperProphecy = $this->prophesize(Scoper::class); @@ -71,7 +72,7 @@ public function test_it_prefixes_the_composer_autoloaders(string $fileContents, $prefix = 'Foo'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $actual = $scoper->scope($filePath, $fileContents, $prefix, $patchers, $whitelist); diff --git a/tests/Scoper/Composer/JsonFileScoperTest.php b/tests/Scoper/Composer/JsonFileScoperTest.php index 87e898f8..99208304 100644 --- a/tests/Scoper/Composer/JsonFileScoperTest.php +++ b/tests/Scoper/Composer/JsonFileScoperTest.php @@ -16,6 +16,7 @@ use Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper\FakeScoper; +use Humbug\PhpScoper\Whitelist; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -38,7 +39,7 @@ public function test_delegates_scoping_to_the_decorated_scoper_if_is_not_a_compo $fileContents = ''; $prefix = 'Humbug'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); /** @var Scoper|ObjectProphecy $decoratedScoperProphecy */ $decoratedScoperProphecy = $this->prophesize(Scoper::class); @@ -71,7 +72,7 @@ public function test_it_prefixes_the_composer_autoloaders(string $fileContents, $prefix = 'Foo'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $actual = $scoper->scope($filePath, $fileContents, $prefix, $patchers, $whitelist); @@ -145,7 +146,7 @@ public function test_it_prefixes_psr0_autoloaders(string $fileContents, string $ $prefix = 'Foo'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $actual = $scoper->scope($filePath, $fileContents, $prefix, $patchers, $whitelist); diff --git a/tests/Scoper/FakeScoper.php b/tests/Scoper/FakeScoper.php index a32eea0b..8b8ce382 100644 --- a/tests/Scoper/FakeScoper.php +++ b/tests/Scoper/FakeScoper.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; use LogicException; final class FakeScoper implements Scoper @@ -22,7 +23,7 @@ final class FakeScoper implements Scoper /** * @inheritdoc */ - public function scope(string $filePath, string $contents, string $prefix, array $patchers, array $whitelist): string + public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string { throw new LogicException(); } diff --git a/tests/Scoper/NullScoperTest.php b/tests/Scoper/NullScoperTest.php index 95c58ebc..5212f815 100644 --- a/tests/Scoper/NullScoperTest.php +++ b/tests/Scoper/NullScoperTest.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; use PHPUnit\Framework\TestCase; use function Humbug\PhpScoper\create_fake_patcher; @@ -37,7 +38,7 @@ public function test_returns_the_file_content_unchanged() $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $scoper = new NullScoper(); diff --git a/tests/Scoper/PatchScoperTest.php b/tests/Scoper/PatchScoperTest.php index 2dd67d4f..ad70650e 100644 --- a/tests/Scoper/PatchScoperTest.php +++ b/tests/Scoper/PatchScoperTest.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -72,7 +73,7 @@ function (string $patcherFilePath, string $patcherPrefix, string $contents) use }, ]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $this->decoratedScoperProphecy ->scope($filePath, $contents, $prefix, $patchers, $whitelist) diff --git a/tests/Scoper/PhpScoperTest.php b/tests/Scoper/PhpScoperTest.php index 218c0443..f2c24a05 100644 --- a/tests/Scoper/PhpScoperTest.php +++ b/tests/Scoper/PhpScoperTest.php @@ -19,6 +19,7 @@ use Humbug\PhpScoper\PhpParser\TraverserFactory; use Humbug\PhpScoper\Reflector; use Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\Whitelist; use LogicException; use PhpParser\Error as PhpParserError; use PhpParser\Node\Name; @@ -153,7 +154,7 @@ public function test_can_scope_a_PHP_file() $prefix = 'Humbug'; $filePath = 'file.php'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $contents = <<<'PHP' decoratedScoperProphecy ->scope($filePath, $fileContents, $prefix, $patchers, $whitelist) @@ -213,7 +214,7 @@ public function test_can_scope_a_PHP_file_with_the_wrong_extension() $prefix = 'Humbug'; $filePath = 'file'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $contents = <<<'PHP' scoper->scope($filePath, $contents, $prefix, $patchers, $whitelist); @@ -343,7 +344,7 @@ public function test_creates_a_new_traverser_for_each_file() $prefix = 'Humbug'; $patchers = [create_fake_patcher()]; - $whitelist = ['Foo']; + $whitelist = Whitelist::create('Foo'); $this->decoratedScoperProphecy ->scope(Argument::any(), Argument::any(), $prefix, $patchers, $whitelist) @@ -419,7 +420,7 @@ function (...$args) use (&$i): bool { /** * @dataProvider provideValidFiles */ - public function test_can_scope_valid_files(string $spec, string $contents, string $prefix, array $whitelist, string $expected) + public function test_can_scope_valid_files(string $spec, string $contents, string $prefix, Whitelist $whitelist, string $expected) { $filePath = 'file.php'; @@ -541,7 +542,7 @@ private function parseSpecFile(array $meta, $fixtureTitle, $fixtureSet): Generat $spec, $payloadParts[0], // Input $fixtureSet['prefix'] ?? $meta['prefix'], - $fixtureSet['whitelist'] ?? $meta['whitelist'], + Whitelist::create(...($fixtureSet['whitelist'] ?? $meta['whitelist'])), $payloadParts[1], // Expected output ]; } From fe1f07188c01dd5ddd42737ce84010cc4bb0a265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Tue, 5 Jun 2018 09:23:24 +0100 Subject: [PATCH 2/9] Implement whitelist for namespaces --- src/PhpParser/NodeVisitor/NameStmtPrefixer.php | 14 +++++++++----- .../NodeVisitor/NamespaceStmtPrefixer.php | 11 +++++++++-- src/PhpParser/TraverserFactory.php | 4 ++-- src/Whitelist.php | 17 +++++++++++++---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/PhpParser/NodeVisitor/NameStmtPrefixer.php b/src/PhpParser/NodeVisitor/NameStmtPrefixer.php index af4c790f..728cd167 100644 --- a/src/PhpParser/NodeVisitor/NameStmtPrefixer.php +++ b/src/PhpParser/NodeVisitor/NameStmtPrefixer.php @@ -16,6 +16,7 @@ use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver; use Humbug\PhpScoper\Reflector; +use Humbug\PhpScoper\Whitelist; use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\ConstFetch; @@ -58,20 +59,18 @@ final class NameStmtPrefixer extends NodeVisitorAbstract ]; private $prefix; + private $whitelist; private $nameResolver; private $reflector; - /** - * @param string $prefix - * @param FullyQualifiedNameResolver $nameResolver - * @param Reflector $reflector - */ public function __construct( string $prefix, + Whitelist $whitelist, FullyQualifiedNameResolver $nameResolver, Reflector $reflector ) { $this->prefix = $prefix; + $this->whitelist = $whitelist; $this->nameResolver = $nameResolver; $this->reflector = $reflector; } @@ -147,6 +146,11 @@ private function prefixName(Name $name): Node return $resolvedName; } + // Skip if the node namespace is whitelisted + if ($this->whitelist->isNamespaceWhitelisted((string) $resolvedName->slice(0, -1))) { + return $resolvedName; + } + // Check if the class can be prefixed if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)) { if ($this->reflector->isClassInternal($resolvedName->toString())) { diff --git a/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php b/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php index d4a042d5..14016cf0 100644 --- a/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php +++ b/src/PhpParser/NodeVisitor/NamespaceStmtPrefixer.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\PhpParser\NodeVisitor; use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\Whitelist; use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Class_; @@ -41,11 +42,13 @@ final class NamespaceStmtPrefixer extends NodeVisitorAbstract { private $prefix; + private $whitelist; private $namespaceStatements; - public function __construct(string $prefix, NamespaceStmtCollection $namespaceStatements) + public function __construct(string $prefix, Whitelist $whitelist, NamespaceStmtCollection $namespaceStatements) { $this->prefix = $prefix; + $this->whitelist = $whitelist; $this->namespaceStatements = $namespaceStatements; } @@ -75,7 +78,7 @@ private function prefixNamespaceStmt(Namespace_ $namespace): Node return $namespace; } - private function isWhitelistedNode(Node $node) + private function isWhitelistedNode(Node $node): bool { if (($node instanceof Class_ || $node instanceof Interface_)) { return true; @@ -95,6 +98,10 @@ private function isWhitelistedNode(Node $node) private function shouldPrefixStmt(Namespace_ $namespace): bool { + if ($this->whitelist->isNamespaceWhitelisted((string) $namespace->name)) { + return false; + } + return null === $namespace->name || (null !== $namespace->name && $this->prefix !== $namespace->name->getFirst()); } } diff --git a/src/PhpParser/TraverserFactory.php b/src/PhpParser/TraverserFactory.php index 700e723c..93b5853c 100644 --- a/src/PhpParser/TraverserFactory.php +++ b/src/PhpParser/TraverserFactory.php @@ -44,12 +44,12 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter $traverser->addVisitor(new NodeVisitor\AppendParentNode()); - $traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $namespaceStatements)); + $traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $whitelist, $namespaceStatements)); $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements)); $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $this->reflector)); - $traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $nameResolver, $this->reflector)); + $traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector)); $traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $this->reflector)); $traverser->addVisitor(new NodeVisitor\WhitelistedClassAppender($whitelist)); diff --git a/src/Whitelist.php b/src/Whitelist.php index c0007ce3..45532abb 100644 --- a/src/Whitelist.php +++ b/src/Whitelist.php @@ -37,10 +37,8 @@ public static function create(string ...$elements): self ); } - $lastElement = $element[strlen($element) - 1]; - - if ('*' === $lastElement) { - $namespaces[] = substr($element, 0, -1); + if ('\*' === substr($element, -2)) { + $namespaces[] = substr($element, 0, -2); } else { $classes[] = $element; } @@ -64,6 +62,17 @@ public function isClassWhitelisted(string $name): bool return in_array($name, $this->classes, true); } + public function isNamespaceWhitelisted(string $name): bool + { + foreach ($this->namespaces as $namespace) { + if (0 === strpos($name, $namespace)) { + return true; + } + } + + return false; + } + /** * {@inheritdoc} */ From 0a1f0e788d6fd13ba315d52416b4280991120108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Wed, 6 Jun 2018 23:29:37 +0100 Subject: [PATCH 3/9] Add more tests --- .../class-const/global-scope-single-level.php | 21 +++ .../global-scope-single-level.php | 21 +++ specs/class/abstract.php | 161 ++++++++++++++++-- specs/class/anonymous.php | 149 +++++++++++++++- specs/const/global-scope-global.php | 16 ++ specs/exp/catch.php | 96 ++++++++++- specs/func-declaration/namespace.php | 89 ++++++++++ specs/string-literal/var.php | 23 +++ .../NodeVisitor/StringScalarPrefixer.php | 9 +- .../NodeVisitor/WhitelistedClassAppender.php | 5 +- src/PhpParser/TraverserFactory.php | 2 +- src/Whitelist.php | 4 +- tests/WhitelistTest.php | 12 ++ 13 files changed, 587 insertions(+), 21 deletions(-) create mode 100644 tests/WhitelistTest.php diff --git a/specs/class-const/global-scope-single-level.php b/specs/class-const/global-scope-single-level.php index 8d658791..b41bf79f 100644 --- a/specs/class-const/global-scope-single-level.php +++ b/specs/class-const/global-scope-single-level.php @@ -43,6 +43,27 @@ class Command } \Humbug\Command::MAIN_CONST; +PHP + ], + + 'Constant call on a class belonging to the global namespace which is whitelisted: add root namespace statement' => [ + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + [], ], - 'Declaration in the global namespace: add prefixed namespace.' => <<<'PHP' + 'Declaration in the global namespace: add prefixed namespace' => <<<'PHP' <<<'PHP' + 'Declaration in the global namespace with the global namespace whitelisted: add root namespace statement' => [ + '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 + , + 'whitelist' => ['A'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['A', '\*'], + 'payload' => <<<'PHP' + <<<'PHP' [ + [ + 'spec' => <<<'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' + [ + 'whitelist' => ['Foo\*'], + 'payload' => <<<'PHP' + [ 'whitelist' => ['Foo\A'], 'payload' => <<<'PHP' [ + 'Declaration of a whitelisted class with FQCN for the whitelist: append aliasing' => [ 'whitelist' => ['\Foo\A'], 'payload' => <<<'PHP' <<<'SPEC' -Multiple declarations in different namespaces with whitelisted classes: prefix each namespace. -SPEC - , + 'Multiple declarations in different namespaces with whitelisted classes: prefix each namespace' => [ 'whitelist' => ['Foo\WA', 'Bar\WB', 'WC'], 'payload' => <<<'PHP' [], ], - 'Declaration in the global namespace: prefix non-internal classes.' => <<<'PHP' + 'Declaration in the global namespace: prefix non-internal classes' => <<<'PHP' <<<'PHP' + 'Declaration in the global namespace which is whitelisted: add root namespace statement' => [ + '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 + , + 'whitelist' => ['A', 'C'], + 'payload' => <<<'PHP' + <<<'PHP' [ + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + <<<'PHP' + 'Catch a custom exception class' => <<<'PHP' <<<'PHP' + 'Catch a whitelisted custom exception class' => [ + 'whitelist' => ['FooException'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + <<<'PHP' [ + 'whitelist' => ['Acme\FooException'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['Acme\*'], + 'payload' => <<<'PHP' + <<<'PHP' <<<'SPEC' +Function declaration in a whitelisted namespace: +- prefix the namespace statements +- prefix the appropriate classes +SPEC + , + 'whitelist' => ['Pi\*'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['Symfony\Component\*'], + 'payload' => <<<'PHP' +prefix = $prefix; + $this->whitelist = $whitelist; $this->reflector = $reflector; } @@ -109,10 +112,12 @@ private function prefixStringScalar(String_ $string): Node // Skip if is already prefixed if ($this->prefix === $stringName->getFirst()) { $newStringName = $stringName; - // Check if the class can be prefixed: class from the global namespace + // 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()) + || $this->whitelist->isNamespaceWhitelisted((string) $stringName->slice(0, -1)) ) { $newStringName = $stringName; } else { diff --git a/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php b/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php index 49c0db02..0315e356 100644 --- a/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php +++ b/src/PhpParser/NodeVisitor/WhitelistedClassAppender.php @@ -88,7 +88,10 @@ private function appendToNamespaceStmt(Namespace_ $namespace): Namespace_ } /** @var Class_ $stmt */ - $name = FullyQualified::concat((string) $namespace->name, (string) $stmt->name); + $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)) { diff --git a/src/PhpParser/TraverserFactory.php b/src/PhpParser/TraverserFactory.php index 93b5853c..e209074f 100644 --- a/src/PhpParser/TraverserFactory.php +++ b/src/PhpParser/TraverserFactory.php @@ -50,7 +50,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $this->reflector)); $traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector)); - $traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $this->reflector)); + $traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $this->reflector)); $traverser->addVisitor(new NodeVisitor\WhitelistedClassAppender($whitelist)); diff --git a/src/Whitelist.php b/src/Whitelist.php index 45532abb..219143a8 100644 --- a/src/Whitelist.php +++ b/src/Whitelist.php @@ -39,6 +39,8 @@ public static function create(string ...$elements): self if ('\*' === substr($element, -2)) { $namespaces[] = substr($element, 0, -2); + } elseif ('*' === $element) { + $namespaces[] = ''; } else { $classes[] = $element; } @@ -65,7 +67,7 @@ public function isClassWhitelisted(string $name): bool public function isNamespaceWhitelisted(string $name): bool { foreach ($this->namespaces as $namespace) { - if (0 === strpos($name, $namespace)) { + if (('' === $namespace && '' === $name) || 0 === strpos($name, $namespace)) { return true; } } diff --git a/tests/WhitelistTest.php b/tests/WhitelistTest.php new file mode 100644 index 00000000..0796f003 --- /dev/null +++ b/tests/WhitelistTest.php @@ -0,0 +1,12 @@ + Date: Wed, 6 Jun 2018 23:30:33 +0100 Subject: [PATCH 4/9] Fix CS --- src/Configuration.php | 2 +- src/Console/Command/AddPrefixCommand.php | 4 ++-- src/Scoper.php | 2 +- src/Whitelist.php | 17 +++++++++++++---- tests/WhitelistTest.php | 11 +++++++++-- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Configuration.php b/src/Configuration.php index 10e9495d..02af74e1 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -88,7 +88,7 @@ public static function load(string $path = null, array $paths = []): self * @param [string, string][] $filesWithContents Array of tuple with the first argument being the file path and the second its contents * @param callable[] $patchers List of closures which can alter the content of the files being * scoped. - * @param Whitelist $whitelist List of classes that will not be scoped. + * @param Whitelist $whitelist List of classes that will not be scoped. * @param Closure $globalNamespaceWhitelisters Closure taking a class name from the global namespace as an argument and * returning a boolean which if `true` means the class should be scoped * (i.e. is ignored) or scoped otherwise. diff --git a/src/Console/Command/AddPrefixCommand.php b/src/Console/Command/AddPrefixCommand.php index 3b38ee7f..7b3189e8 100644 --- a/src/Console/Command/AddPrefixCommand.php +++ b/src/Console/Command/AddPrefixCommand.php @@ -167,7 +167,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @var callable[] $patchers + * @var callable[] */ private function scopeFiles( string $prefix, @@ -224,7 +224,7 @@ function ($a, $b) { } /** - * @param callable[] $patchers + * @param callable[] $patchers */ private function scopeFile( string $inputFilePath, diff --git a/src/Scoper.php b/src/Scoper.php index 1100099a..365a5e33 100644 --- a/src/Scoper.php +++ b/src/Scoper.php @@ -25,7 +25,7 @@ interface Scoper * @param string $contents File contents * @param string $prefix Prefix to apply to the file * @param callable[] $patchers - * @param Whitelist $whitelist List of classes to exclude from the scoping. + * @param Whitelist $whitelist List of classes to exclude from the scoping. * * @throws ParsingException * diff --git a/src/Whitelist.php b/src/Whitelist.php index 219143a8..b921dcd3 100644 --- a/src/Whitelist.php +++ b/src/Whitelist.php @@ -2,14 +2,23 @@ declare(strict_types=1); +/* + * This file is part of the humbug/php-scoper package. + * + * Copyright (c) 2017 Théo FIDRY , + * 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; -use function count; use Countable; -use function in_array; use InvalidArgumentException; +use function count; +use function in_array; use function sprintf; -use function strlen; use function substr; use function trim; @@ -82,4 +91,4 @@ public function count(): int { return count($this->classes) + count($this->namespaces); } -} \ No newline at end of file +} diff --git a/tests/WhitelistTest.php b/tests/WhitelistTest.php index 0796f003..73cd4f34 100644 --- a/tests/WhitelistTest.php +++ b/tests/WhitelistTest.php @@ -2,11 +2,18 @@ declare(strict_types=1); +/* + * This file is part of the humbug/php-scoper package. + * + * Copyright (c) 2017 Théo FIDRY , + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ -use Humbug\PhpScoper\Whitelist; use PHPUnit\Framework\TestCase; class WhitelistTest extends TestCase { - } From bda23e61dd11a66604418ad78a054612e7d8a537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Thu, 7 Jun 2018 08:39:04 +0100 Subject: [PATCH 5/9] Add missing tests --- specs/string-literal/var.php | 8 + specs/use/use-class.php | 156 ++++++++++++++++++ .../NodeVisitor/UseStmt/UseStmtPrefixer.php | 10 +- src/PhpParser/TraverserFactory.php | 2 +- src/Whitelist.php | 2 +- tests/WhitelistTest.php | 141 ++++++++++++++++ 6 files changed, 316 insertions(+), 3 deletions(-) diff --git a/specs/string-literal/var.php b/specs/string-literal/var.php index 4be9be78..8eaeade4 100644 --- a/specs/string-literal/var.php +++ b/specs/string-literal/var.php @@ -23,6 +23,10 @@ 'FQCN string argument: transform into a FQCN and prefix it' => <<<'PHP' <<<'SPEC' +Use statement of a whitelisted class belonging to the global scope: +- wrap the statement in a prefixed namespace +- prefix the use statement +- append a class alias statement after the class declaration +SPEC + , + 'whitelist' => ['Foo'], + 'payload' => <<<'PHP' + <<<'SPEC' +Use statement of a class belonging to the global scope which has been whitelisted: +- wrap the statement in a prefixed namespace +- prefix the use statement +- append a class alias statement after the class declaration +SPEC + , + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + <<<'SPEC' +Use statement of a whitelisted class belonging to the global scope which has been whitelisted: +- wrap the statement in a prefixed namespace +- prefix the use statement +- append a class alias statement after the class declaration +SPEC + , + 'whitelist' => ['Foo', '\*'], + 'payload' => <<<'PHP' + <<<'SPEC' +Use statement of two-level class belonging to a whitelisted namespace: +- prefix the namespaces +- prefix the use statement +SPEC + , + 'whitelist' => ['Foo\*'], + 'payload' => <<<'PHP' + <<<'SPEC' +Use statement of whitelisted two-level class belonging to a whitelisted namespace: +- prefix the namespaces +- prefix the use statement +SPEC + , + 'whitelist' => ['Foo', 'Foo\*'], + 'payload' => <<<'PHP' +prefix = $prefix; $this->reflector = $reflector; + $this->whitelist = $whitelist; } /** @@ -59,6 +62,11 @@ private function shouldPrefixUseStmt(UseUse $use): bool return false; } + // If is whitelisted + if ($this->whitelist->isNamespaceWhitelisted((string) $use->name)) { + return false; + } + if (Use_::TYPE_FUNCTION === $useType) { return false === $this->reflector->isFunctionInternal((string) $use->name); } diff --git a/src/PhpParser/TraverserFactory.php b/src/PhpParser/TraverserFactory.php index e209074f..b5cfbaa7 100644 --- a/src/PhpParser/TraverserFactory.php +++ b/src/PhpParser/TraverserFactory.php @@ -47,7 +47,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter $traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $whitelist, $namespaceStatements)); $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements)); - $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $this->reflector)); + $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $this->reflector)); $traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector)); $traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $this->reflector)); diff --git a/src/Whitelist.php b/src/Whitelist.php index b921dcd3..8d5f3c92 100644 --- a/src/Whitelist.php +++ b/src/Whitelist.php @@ -76,7 +76,7 @@ public function isClassWhitelisted(string $name): bool public function isNamespaceWhitelisted(string $name): bool { foreach ($this->namespaces as $namespace) { - if (('' === $namespace && '' === $name) || 0 === strpos($name, $namespace)) { + if ('' === $namespace || 0 === strpos($name, $namespace)) { return true; } } diff --git a/tests/WhitelistTest.php b/tests/WhitelistTest.php index 73cd4f34..e7f4bf2d 100644 --- a/tests/WhitelistTest.php +++ b/tests/WhitelistTest.php @@ -12,8 +12,149 @@ * file that was distributed with this source code. */ +namespace Humbug\PhpScoper; + use PHPUnit\Framework\TestCase; +use Reflection; +use ReflectionClass; +/** + * @covers \Humbug\PhpScoper\Whitelist + */ class WhitelistTest extends TestCase { + /** + * @dataProvider provideWhitelists + */ + public function test_it_can_be_created_from_a_list_of_strings( + array $whitelist, + array $expectedClasses, + array $expectedNamespaces + ) { + $whitelistObject = Whitelist::create(...$whitelist); + + $whitelistReflection = new ReflectionClass(Whitelist::class); + + $whitelistClassReflection = $whitelistReflection->getProperty('classes'); + $whitelistClassReflection->setAccessible(true); + $actualClasses = $whitelistClassReflection->getValue($whitelistObject); + + $whitelistNamespaceReflection = $whitelistReflection->getProperty('namespaces'); + $whitelistNamespaceReflection->setAccessible(true); + $actualNamespaces = $whitelistNamespaceReflection->getValue($whitelistObject); + + $this->assertSame($expectedClasses, $actualClasses); + $this->assertSame($expectedNamespaces, $actualNamespaces); + } + + /** + * @dataProvider provideClassWhitelists + */ + public function test_it_can_tell_if_a_class_is_whitelisted(Whitelist $whitelist, string $class, bool $expected) + { + $actual = $whitelist->isClassWhitelisted($class); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider provideNamespaceWhitelists + */ + public function test_it_can_tell_if_a_namespace_is_whitelisted(Whitelist $whitelist, string $class, bool $expected) + { + $actual = $whitelist->isNamespaceWhitelisted($class); + + $this->assertSame($expected, $actual); + } + + public function provideWhitelists() + { + yield [[], [], []]; + + yield [['Acme\Foo'], ['Acme\Foo'], []]; + + yield [['Acme\Foo\*'], [], ['Acme\Foo']]; + + yield [['\*'], [], ['']]; + + yield [['Acme\Foo', 'Acme\Foo\*', '\*'], ['Acme\Foo'], ['Acme\Foo', '']]; + } + + public function provideClassWhitelists() + { + yield [ + Whitelist::create(), + 'Acme\Foo', + false, + ]; + + yield [ + Whitelist::create('Acme\Foo'), + 'Acme\Foo', + true, + ]; + + yield [ + Whitelist::create('Acme\Foo'), + 'Acme\Foo\Bar', + false, + ]; + + yield [ + Whitelist::create('Acme\Foo'), + 'Acme', + false, + ]; + + yield [ + Whitelist::create('Acme'), + 'Acme', + true, + ]; + + yield [ + Whitelist::create('Acme\*'), + 'Acme', + false, + ]; + } + + public function provideNamespaceWhitelists() + { + yield [ + Whitelist::create(), + 'Acme\Foo', + false, + ]; + + yield [ + Whitelist::create('Acme\Foo\*'), + 'Acme\Foo', + true, + ]; + + yield [ + Whitelist::create('Acme\*'), + 'Acme\Foo', + true, + ]; + + yield [ + Whitelist::create('Acme\Foo\*'), + 'Acme\Foo\Bar', + true, + ]; + + yield [ + Whitelist::create('\*'), + 'Acme', + true, + ]; + + yield [ + Whitelist::create('\*'), + 'Acme\Foo', + true, + ]; + } } From 6a96d1b5c7a451619b06b916df2c81d687d93bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Thu, 7 Jun 2018 08:50:15 +0100 Subject: [PATCH 6/9] Add more tests --- _specs/.gitignore | 1 + .../class-const/namespace-scope-two-level.php | 68 +++++++++++++++++++ specs/class/abstract.php | 27 ++++++++ 3 files changed, 96 insertions(+) create mode 100644 _specs/.gitignore diff --git a/_specs/.gitignore b/_specs/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/_specs/.gitignore @@ -0,0 +1 @@ +* diff --git a/specs/class-const/namespace-scope-two-level.php b/specs/class-const/namespace-scope-two-level.php index d3057562..d5bf965d 100644 --- a/specs/class-const/namespace-scope-two-level.php +++ b/specs/class-const/namespace-scope-two-level.php @@ -118,6 +118,74 @@ class Command \Humbug\X\PHPUnit\Command::MAIN_CONST; +PHP + ], + + [ + 'spec' => <<<'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 + , + '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 + , + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + [ + 'whitelist' => ['\*'], + 'payload' => <<<'PHP' + Date: Thu, 7 Jun 2018 08:51:04 +0100 Subject: [PATCH 7/9] Simplify the code --- src/PhpParser/NodeVisitor/NameStmtPrefixer.php | 2 +- src/PhpParser/NodeVisitor/StringScalarPrefixer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpParser/NodeVisitor/NameStmtPrefixer.php b/src/PhpParser/NodeVisitor/NameStmtPrefixer.php index 728cd167..91445b9e 100644 --- a/src/PhpParser/NodeVisitor/NameStmtPrefixer.php +++ b/src/PhpParser/NodeVisitor/NameStmtPrefixer.php @@ -147,7 +147,7 @@ private function prefixName(Name $name): Node } // Skip if the node namespace is whitelisted - if ($this->whitelist->isNamespaceWhitelisted((string) $resolvedName->slice(0, -1))) { + if ($this->whitelist->isNamespaceWhitelisted((string) $resolvedName)) { return $resolvedName; } diff --git a/src/PhpParser/NodeVisitor/StringScalarPrefixer.php b/src/PhpParser/NodeVisitor/StringScalarPrefixer.php index 4e483f2e..ec40e430 100644 --- a/src/PhpParser/NodeVisitor/StringScalarPrefixer.php +++ b/src/PhpParser/NodeVisitor/StringScalarPrefixer.php @@ -117,7 +117,7 @@ private function prefixStringScalar(String_ $string): Node } elseif ( 1 === count($stringName->parts) || $this->reflector->isClassInternal($stringName->toString()) - || $this->whitelist->isNamespaceWhitelisted((string) $stringName->slice(0, -1)) + || $this->whitelist->isNamespaceWhitelisted((string) $stringName) ) { $newStringName = $stringName; } else { From 8cb6a8ca2266b2bf31d6cc1b52d8fd3cd34de9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Thu, 7 Jun 2018 08:59:03 +0100 Subject: [PATCH 8/9] Add doc --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a05a6dcf..cd4942d8 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ potentially very difficult to debug due to dissimilar or unsupported package ver - [Finders and paths](#finders-and-paths) - [Patchers](#patchers) - [Whitelist][whitelist] + - [Class Whitelisting](#class-whitelisting) + - [Namespace Whitelisting](#namespace-whitelisting) - [Building A Scoped PHAR](#building-a-scoped-phar) - [With Box](#with-box) - [Step 1: Configure build location and prep vendors](#step-1-configure-build-location-and-prep-vendors) @@ -258,7 +260,10 @@ 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 `PHPUnit\Framework\TestCase` class. -A way to achieve this is by specifying a list of classes to not prefix: + +### Class whitelisting + +You can whitelist classes and interfaces like so: ```php [ + 'PHPUnit\Framework\*', + ], +]; +``` + +Now anything under the `PHPUnit\Framework` namespace will not be prefixed. +Note this works as well for the global namespace: + +```php + [ + '\*', + ], +]; +``` ## Building A Scoped PHAR From 7964722844f1a74b6312b31cf8aaa4ce63b27896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Thu, 7 Jun 2018 09:02:56 +0100 Subject: [PATCH 9/9] Add more tests still --- README.md | 2 +- tests/WhitelistTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd4942d8..f1e3b0e0 100644 --- a/README.md +++ b/README.md @@ -311,7 +311,7 @@ Note this works as well for the global namespace: return [ 'whitelist' => [ - '\*', + '*', ], ]; ``` diff --git a/tests/WhitelistTest.php b/tests/WhitelistTest.php index e7f4bf2d..f0e282b9 100644 --- a/tests/WhitelistTest.php +++ b/tests/WhitelistTest.php @@ -73,10 +73,14 @@ public function provideWhitelists() yield [['Acme\Foo'], ['Acme\Foo'], []]; + yield [['\Acme\Foo'], ['Acme\Foo'], []]; + yield [['Acme\Foo\*'], [], ['Acme\Foo']]; yield [['\*'], [], ['']]; + yield [['*'], [], ['']]; + yield [['Acme\Foo', 'Acme\Foo\*', '\*'], ['Acme\Foo'], ['Acme\Foo', '']]; }