From 20086856b1adc81cf0c4da0bc0c9a59530fd191e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 3 Aug 2018 14:43:48 +0200 Subject: [PATCH] Record whitelisted classes (#251) --- .../string-literal/array-var.php | 0 .../string-literal/class-const-array.php | 0 .../string-literal/class-const.php | 0 .../string-literal/class-prop.php | 0 .../string-literal/class-var.php | 0 {specs => fixtures}/string-literal/const.php | 3 + .../string-literal/define-value.php | 0 .../string-literal/func-arg.php | 3 + .../string-literal/method-arg.php | 0 {specs => fixtures}/string-literal/misc.php | 0 {specs => fixtures}/string-literal/new.php | 3 + {specs => fixtures}/string-literal/var.php | 3 + specs/class-FQ.php | 15 ++++ ...-level-with-single-level-use-and-alias.php | 3 + ...-scope-two-level-with-single-level-use.php | 6 ++ specs/class-const/global-scope-two-level.php | 6 ++ ...-level-with-single-level-use-and-alias.php | 3 + ...-scope-two-level-with-single-level-use.php | 6 ++ .../class-const/namespace-scope-two-level.php | 6 ++ ...-level-with-single-level-use-and-alias.php | 3 + ...-scope-two-level-with-single-level-use.php | 6 ++ .../global-scope-two-level.php | 6 ++ ...-level-with-single-level-use-and-alias.php | 3 + ...-scope-two-level-with-single-level-use.php | 6 ++ .../namespace-scope-two-level.php | 6 ++ specs/class/abstract.php | 72 +++++++++++++++- specs/class/anonymous.php | 4 + specs/class/conditional.php | 6 ++ specs/class/final.php | 3 + specs/class/interface.php | 56 ++++++++++++ specs/class/regular-extend.php | 3 + specs/class/regular.php | 12 +++ specs/func-declaration/global.php | 9 ++ specs/func-declaration/method.php | 6 ++ specs/func-declaration/namespace.php | 12 +++ ...-parts-with-single-level-use-and-alias.php | 6 ++ ...-scope-two-parts-with-single-level-use.php | 12 +++ specs/new/global-scope-two-parts.php | 6 ++ ...namespace-two-parts-with-two-level-use.php | 3 + ...-parts-with-single-level-use-and-alias.php | 9 ++ ...-scope-two-parts-with-single-level-use.php | 12 +++ .../static-method/global-scope-two-parts.php | 6 ++ ...namespace-two-parts-with-two-level-use.php | 3 + specs/static-method/namespace-two-parts.php | 3 + specs/use/use-class.php | 9 ++ specs/whitelist-case-sensitiveness.php | 3 + .../NodeVisitor/ClassIdentifierRecorder.php | 85 +++++++++++++++++++ .../FunctionIdentifierRecorder.php | 2 +- src/PhpParser/TraverserFactory.php | 1 + tests/Scoper/PhpScoperSpecTest.php | 74 +++++++++++----- 50 files changed, 480 insertions(+), 24 deletions(-) rename {specs => fixtures}/string-literal/array-var.php (100%) rename {specs => fixtures}/string-literal/class-const-array.php (100%) rename {specs => fixtures}/string-literal/class-const.php (100%) rename {specs => fixtures}/string-literal/class-prop.php (100%) rename {specs => fixtures}/string-literal/class-var.php (100%) rename {specs => fixtures}/string-literal/const.php (97%) rename {specs => fixtures}/string-literal/define-value.php (100%) rename {specs => fixtures}/string-literal/func-arg.php (99%) rename {specs => fixtures}/string-literal/method-arg.php (100%) rename {specs => fixtures}/string-literal/misc.php (100%) rename {specs => fixtures}/string-literal/new.php (98%) rename {specs => fixtures}/string-literal/var.php (97%) create mode 100644 src/PhpParser/NodeVisitor/ClassIdentifierRecorder.php diff --git a/specs/string-literal/array-var.php b/fixtures/string-literal/array-var.php similarity index 100% rename from specs/string-literal/array-var.php rename to fixtures/string-literal/array-var.php diff --git a/specs/string-literal/class-const-array.php b/fixtures/string-literal/class-const-array.php similarity index 100% rename from specs/string-literal/class-const-array.php rename to fixtures/string-literal/class-const-array.php diff --git a/specs/string-literal/class-const.php b/fixtures/string-literal/class-const.php similarity index 100% rename from specs/string-literal/class-const.php rename to fixtures/string-literal/class-const.php diff --git a/specs/string-literal/class-prop.php b/fixtures/string-literal/class-prop.php similarity index 100% rename from specs/string-literal/class-prop.php rename to fixtures/string-literal/class-prop.php diff --git a/specs/string-literal/class-var.php b/fixtures/string-literal/class-var.php similarity index 100% rename from specs/string-literal/class-var.php rename to fixtures/string-literal/class-var.php diff --git a/specs/string-literal/const.php b/fixtures/string-literal/const.php similarity index 97% rename from specs/string-literal/const.php rename to fixtures/string-literal/const.php index f9f5d87b..c9f316df 100644 --- a/specs/string-literal/const.php +++ b/fixtures/string-literal/const.php @@ -153,6 +153,9 @@ class Yaml 'FQC constant call on whitelisted class' => [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'registered-classes' => [ + ['Symfony\Component\Yaml\Yaml', 'Humbug\Symfony\Component\Yaml\Yaml'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'registered-classes' => [ + ['Symfony\Component\Yaml\Yaml', 'Humbug\Symfony\Component\Yaml\Yaml'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'registered-classes' => [ + ['Symfony\Component\Yaml\Yaml', 'Humbug\Symfony\Component\Yaml\Yaml'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Symfony\Component\Yaml\Yaml'], + 'registered-classes' => [ + ['Symfony\Component\Yaml\Yaml', 'Humbug\Symfony\Component\Yaml\Yaml'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar', 'Foo\Bar\Poz'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ['Foo\Bar\Poz', 'Humbug\Foo\Bar\Poz'], + ], 'payload' => <<<'PHP' [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ['Foo\Bar\Poz', 'Humbug\Foo\Bar\Poz'], + + ['A\Foo', 'Humbug\A\Foo'], + ['A\Foo\Bar', 'Humbug\A\Foo\Bar'], + ['A\Foo\Bar\Poz', 'Humbug\A\Foo\Bar\Poz'], + ['A\Aoo', 'Humbug\A\Aoo'], + ['A\Aoo\Aoz', 'Humbug\A\Aoo\Aoz'], + ['A\Aoo\Aoz\Poz', 'Humbug\A\Aoo\Aoz\Poz'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['PHPUnit\Command'], + 'registered-classes' => [ + ['PHPUnit\Command', 'Humbug\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['PHPUnit\Command'], + 'registered-classes' => [ + ['PHPUnit\Command', 'Humbug\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\PHPUnit\Command'], + 'registered-classes' => [ + ['X\PHPUnit\Command', 'Humbug\X\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['PHPUnit\Command'], + 'registered-classes' => [ + ['PHPUnit\Command', 'Humbug\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['PHPUnit\Command'], + 'registered-classes' => [ + ['PHPUnit\Command', 'Humbug\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['PHPUnit\Command'], + 'registered-classes' => [ + ['PHPUnit\Command', 'Humbug\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\PHPUnit\Command'], + 'registered-classes' => [ + ['X\PHPUnit\Command', 'Humbug\X\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['PHPUnit\Command'], + 'registered-classes' => [ + ['PHPUnit\Command', 'Humbug\PHPUnit\Command'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['A'], + 'registered-classes' => [ + ['A', 'Humbug\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['A', '\*'], + 'registered-classes' => [ + ['A', 'Humbug\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\A'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ], 'payload' => <<<'PHP' [ + 'Declaration of a namespaced class whitelisted with a pattern' => [ + 'whitelist' => ['Foo\A*'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ['Foo\AA', 'Humbug\Foo\AA'], + ['Foo\A\B', 'Humbug\Foo\A\B'], + ], + 'payload' => <<<'PHP' + [ 'whitelist' => ['\Foo\A'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\WA', 'Bar\WB', 'WC'], + 'registered-classes' => [ + ['Foo\WA', 'Humbug\Foo\WA'], + ['Bar\WB', 'Humbug\Bar\WB'], + ['WC', 'Humbug\WC'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['A', 'C'], + 'registered-classes' => [ + ['A', 'Humbug\A'], + ['C', 'Humbug\C'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['A'], + 'registered-classes' => [ + ['A', 'Humbug\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\A'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\A'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\A'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ], 'payload' => <<<'PHP' [ + 'whitelist' => ['Foo\A*'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ['Foo\AA', 'Humbug\Foo\AA'], + ['Foo\A\B', 'Humbug\Foo\A\B'], ], + 'payload' => <<<'PHP' + <<<'PHP' [ 'whitelist' => ['Foo\B'], + 'registered-classes' => [ + ['Foo\B', 'Humbug\Foo\B'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\A'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\A*'], + 'registered-classes' => [ + ['Foo\A', 'Humbug\Foo\A'], + ['Foo\AA', 'Humbug\Foo\AA'], + ['Foo\A\B', 'Humbug\Foo\A\B'], + ], 'payload' => <<<'PHP' [ + ['Foo\A', 'Humbug\Foo\A'], + ['Bar\B', 'Humbug\Bar\B'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Y', 'BAR_CONST'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'registered-functions' => [ ['foo', 'Humbug\foo'], ], @@ -180,6 +183,9 @@ function foo(string $foo = \FOO_CONST) 'Function declaration in the global namespace with use statements' => [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'registered-functions' => [ ['foo', 'Humbug\foo'], ], @@ -268,6 +274,9 @@ function foo(string $arg0, ?string $arg1, ?string $arg2 = null, \Humbug\Foo $arg 'Function declarations with return types in the global namespace with use statements' => [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'registered-functions' => [ ['foo', 'Humbug\foo'], ], diff --git a/specs/func-declaration/method.php b/specs/func-declaration/method.php index b5cbfa29..93893b38 100644 --- a/specs/func-declaration/method.php +++ b/specs/func-declaration/method.php @@ -26,6 +26,9 @@ 'Method declarations' => [ 'whitelist' => ['X\Y', 'BAR_CONST'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Y'], + 'registered-classes' => [ + ['X\Y', 'Humbug\X\Y'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Foo\Bar'], + 'registered-classes' => [ + ['X\Foo\Bar', 'Humbug\X\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Foo\Bar'], + 'registered-classes' => [ + ['X\Foo\Bar', 'Humbug\X\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['X\Foo\Bar'], + 'registered-classes' => [ + ['X\Foo\Bar', 'Humbug\X\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo'], + 'registered-classes' => [ + ['Foo', 'Humbug\Foo'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo', '\*'], + 'registered-classes' => [ + ['Foo', 'Humbug\Foo'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['Foo\Bar'], + 'registered-classes' => [ + ['Foo\Bar', 'Humbug\Foo\Bar'], + ], 'payload' => <<<'PHP' [ 'whitelist' => ['acme\foo'], + 'registered-classes' => [ + ['Acme\Foo', 'Humbug\Acme\Foo'], + ], 'payload' => <<<'PHP' , + * 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\NodeVisitor\Resolver\FullyQualifiedNameResolver; +use Humbug\PhpScoper\Whitelist; +use PhpParser\Node; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt\ClassLike; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\NodeVisitorAbstract; + +/** + * Records the user classes registered in the global namespace which have been whitelisted and whitelisted classes. + * + * @private + */ +final class ClassIdentifierRecorder extends NodeVisitorAbstract +{ + private $prefix; + private $nameResolver; + private $whitelist; + + public function __construct( + string $prefix, + FullyQualifiedNameResolver $nameResolver, + Whitelist $whitelist + ) { + $this->prefix = $prefix; + $this->nameResolver = $nameResolver; + $this->whitelist = $whitelist; + } + + /** + * @inheritdoc + */ + public function enterNode(Node $node): Node + { + if (false === ($node instanceof Identifier) || false === ParentNodeAppender::hasParent($node)) { + return $node; + } + + $parent = ParentNodeAppender::getParent($node); + + if (false === ($parent instanceof ClassLike) || $parent instanceof Trait_) { + return $node; + } + /** @var ClassLike $parent */ + if (null === $parent->name) { + return $node; + } + + /** @var Identifier $node */ + $resolvedName = $this->nameResolver->resolveName($node)->getName(); + + if (false === ($resolvedName instanceof FullyQualified)) { + return $node; + } + + /** @var FullyQualified $resolvedName */ + if ($this->whitelist->isGlobalWhitelistedClass((string) $resolvedName) + || $this->whitelist->isSymbolWhitelisted((string) $resolvedName) + ) { + $this->whitelist->recordWhitelistedClass( + $resolvedName, + FullyQualified::concat($this->prefix, $resolvedName) + ); + } + + return $node; + } +} diff --git a/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php b/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php index aa2c4668..5e2808ef 100644 --- a/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php +++ b/src/PhpParser/NodeVisitor/FunctionIdentifierRecorder.php @@ -23,7 +23,7 @@ use PhpParser\NodeVisitorAbstract; /** - * Records the user functions registered in the global namespace which have been whitelisted. + * Records the user functions registered in the global namespace which have been whitelisted and whitelisted functions. * * @private */ diff --git a/src/PhpParser/TraverserFactory.php b/src/PhpParser/TraverserFactory.php index 5a557af7..9f0470dd 100644 --- a/src/PhpParser/TraverserFactory.php +++ b/src/PhpParser/TraverserFactory.php @@ -50,6 +50,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter $traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $this->reflector)); $traverser->addVisitor(new NodeVisitor\FunctionIdentifierRecorder($prefix, $nameResolver, $whitelist)); + $traverser->addVisitor(new NodeVisitor\ClassIdentifierRecorder($prefix, $nameResolver, $whitelist)); $traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector)); $traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $this->reflector)); diff --git a/tests/Scoper/PhpScoperSpecTest.php b/tests/Scoper/PhpScoperSpecTest.php index 02047145..9f21292b 100644 --- a/tests/Scoper/PhpScoperSpecTest.php +++ b/tests/Scoper/PhpScoperSpecTest.php @@ -27,18 +27,21 @@ use Roave\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; use Throwable; use UnexpectedValueException; use const PHP_EOL; use function array_diff; use function array_keys; use function array_map; +use function basename; use function current; use function Humbug\PhpScoper\create_fake_patcher; use function Humbug\PhpScoper\create_parser; use function implode; use function is_array; use function sprintf; +use function usort; class PhpScoperSpecTest extends TestCase { @@ -80,6 +83,7 @@ public function test_it_uses_the_right_specs_directory() * @dataProvider provideValidFiles */ public function test_can_scope_valid_files( + string $file, string $spec, string $contents, string $prefix, @@ -111,6 +115,7 @@ public function test_can_scope_valid_files( } $specMessage = $this->createSpecMessage( + $file, $spec, $contents, $whitelist, @@ -120,34 +125,33 @@ public function test_can_scope_valid_files( $expectedRegisteredFunctions ); - $this->assertSame( - $expected, - $actual, - $specMessage - ); - $this->assertSame( - $whitelist->getRecordedWhitelistedClasses(), - $expectedRegisteredClasses, - $specMessage - ); - $this->assertSame( - $whitelist->getRecordedWhitelistedFunctions(), - $expectedRegisteredFunctions, - $specMessage - ); + $this->assertSame($expected, $actual, $specMessage); + + $actualRecordedWhitelistedClasses = $whitelist->getRecordedWhitelistedClasses(); + + $this->assertSameRecordedSymbols($actualRecordedWhitelistedClasses, $expectedRegisteredClasses, $specMessage); + + $actualRecordedWhitelistedFunctions = $whitelist->getRecordedWhitelistedFunctions(); + + $this->assertSameRecordedSymbols($actualRecordedWhitelistedFunctions, $expectedRegisteredFunctions, $specMessage); } public function provideValidFiles() { - $files = (new Finder())->files()->in(self::SECONDARY_SPECS_PATH); + $sourceDir = self::SECONDARY_SPECS_PATH; + + $files = (new Finder())->files()->in($sourceDir); if (0 === count($files)) { - $files = (new Finder())->files()->in(self::SPECS_PATH); + $sourceDir = self::SPECS_PATH; + + $files = (new Finder())->files()->in($sourceDir); } $files->sortByName(); foreach ($files as $file) { + /* @var SplFileInfo $file */ try { $fixtures = include $file; @@ -155,7 +159,12 @@ public function provideValidFiles() unset($fixtures['meta']); foreach ($fixtures as $fixtureTitle => $fixtureSet) { - yield $this->parseSpecFile($meta, $fixtureTitle, $fixtureSet)->current(); + yield $this->parseSpecFile( + basename($sourceDir).'/'.$file->getRelativePathname(), + $meta, + $fixtureTitle, + $fixtureSet + )->current(); } } catch (Throwable $throwable) { $this->fail( @@ -193,13 +202,10 @@ private function createScoper(string $contents): Scoper } /** - * @param array $meta * @param string|int $fixtureTitle * @param string|array $fixtureSet - * - * @return Generator */ - private function parseSpecFile(array $meta, $fixtureTitle, $fixtureSet): Generator + private function parseSpecFile(string $file, array $meta, $fixtureTitle, $fixtureSet): Generator { $spec = sprintf( '[%s] %s', @@ -238,6 +244,7 @@ private function parseSpecFile(array $meta, $fixtureTitle, $fixtureSet): Generat } yield [ + $file, $spec, $payloadParts[0], // Input $fixtureSet['prefix'] ?? $meta['prefix'], @@ -257,6 +264,7 @@ private function parseSpecFile(array $meta, $fixtureTitle, $fixtureSet): Generat * @param string[][] $expectedRegisteredFunctions */ private function createSpecMessage( + string $file, string $spec, string $contents, Whitelist $whitelist, @@ -292,6 +300,7 @@ private function createSpecMessage( SPECIFICATION $titleSeparator $spec +$file $titleSeparator INPUT @@ -383,4 +392,25 @@ private function convertBoolToString(bool $bool): string { return true === $bool ? 'true' : 'false'; } + + /** + * @param string[][] $expected + * @param string[][] $actual + */ + private function assertSameRecordedSymbols(array $expected, array $actual, string $message): void + { + $sort = function (array $a, array $b): int { + /* + * @var string[] $a + * @var string[] $b + */ + + return $a[0] <=> $b[0]; + }; + + usort($expected, $sort); + usort($actual, $sort); + + $this->assertSame($expected, $actual, $message); + } }