From 909fa759d30994bb93ab271acda71f358214e317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Mon, 4 Sep 2017 21:48:49 +0100 Subject: [PATCH 1/9] Fix declaration tests --- specs/class/conditional.php | 28 ++++----- specs/class/final.php | 22 +++---- specs/class/interface.php | 28 ++++----- specs/class/trait.php | 112 ++++++++++++++++----------------- tests/Scoper/PhpScoperTest.php | 1 - 5 files changed, 91 insertions(+), 100 deletions(-) diff --git a/specs/class/conditional.php b/specs/class/conditional.php index aa40ecfd..b98988a9 100644 --- a/specs/class/conditional.php +++ b/specs/class/conditional.php @@ -83,27 +83,25 @@ class C {} ---- markTestIncomplete('TODO'); $filePath = escape_path($this->tmp.'/file.php'); touch($filePath); From 04e236f791cd7e8d4b9cc64c4443b82f85b0b06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Tue, 5 Sep 2017 20:32:27 +0100 Subject: [PATCH 2/9] Big WIP... --- _specs/class-FQ.php | 383 ++++++++++++++++++ ...h-single-level-use-statement-and-alias.php | 12 +- ...-level-with-single-level-use-statement.php | 14 +- ...-level-with-single-level-use-and-alias.php | 5 - ...-scope-two-level-with-single-level-use.php | 4 - ...h-single-level-use-statement-and-alias.php | 4 - .../namespace-scope-single-level.php | 3 - src/Handler/HandleAddPrefix.php | 2 +- ...ntNodeVisitor.php => AppendParentNode.php} | 18 +- .../NamespaceStmtCollection.php | 25 +- .../{ => Collection}/UseStmtCollection.php | 20 +- .../ScopeFunctionCallStmtNodeVisitor.php | 8 +- .../ScopeStaticCallStmtNodeVisitor.php | 8 +- src/NodeVisitor/IgnoreNodeUtility.php | 29 ++ ...rNodeVisitor.php => IgnoreNodeVisitor.php} | 2 +- src/NodeVisitor/NameResolver.php | 106 +++++ ...Visitor.php => NamespaceStmtCollector.php} | 3 +- .../NewStmt/ScopeNewStmtNodeVisitor.php | 24 +- .../ScopeSingleLevelNewStmtNodeVisitor.php | 8 +- .../Resolver/FullyQualifiedNameResolver.php | 49 +++ src/NodeVisitor/ScopeConstStmtNodeVisitor.php | 6 +- .../UseStmt/ScopeUseStmtNodeVisitor.php | 166 -------- ...mtNodeVisitor.php => UseStmtCollector.php} | 4 +- src/NodeVisitor/UseStmt/UseStmtPrefixer.php | 107 +++++ src/Scoper/PhpScoper.php | 4 +- src/Scoper/TraverserFactory.php | 59 +-- .../NativeTraverserFactory.php | 81 ++++ src/functions.php | 4 +- .../NodeVisitor/Resolver/NameResolverTest.php | 194 +++++++++ tests/Scoper/PhpScoperTest.php | 46 ++- 30 files changed, 1098 insertions(+), 300 deletions(-) create mode 100644 _specs/class-FQ.php rename src/NodeVisitor/{ParentNodeVisitor.php => AppendParentNode.php} (68%) rename src/NodeVisitor/{ => Collection}/NamespaceStmtCollection.php (65%) rename src/NodeVisitor/{ => Collection}/UseStmtCollection.php (70%) create mode 100644 src/NodeVisitor/IgnoreNodeUtility.php rename src/NodeVisitor/{IgnoreNamespaceScoperNodeVisitor.php => IgnoreNodeVisitor.php} (98%) create mode 100644 src/NodeVisitor/NameResolver.php rename src/NodeVisitor/{CollectNamespaceStmtNodeVisitor.php => NamespaceStmtCollector.php} (87%) create mode 100644 src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php delete mode 100644 src/NodeVisitor/UseStmt/ScopeUseStmtNodeVisitor.php rename src/NodeVisitor/UseStmt/{CollectUseStmtNodeVisitor.php => UseStmtCollector.php} (87%) create mode 100644 src/NodeVisitor/UseStmt/UseStmtPrefixer.php create mode 100644 src/Scoper/TraverserFactory/NativeTraverserFactory.php create mode 100644 tests/NodeVisitor/Resolver/NameResolverTest.php diff --git a/_specs/class-FQ.php b/_specs/class-FQ.php new file mode 100644 index 00000000..68044596 --- /dev/null +++ b/_specs/class-FQ.php @@ -0,0 +1,383 @@ +, + * 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' => 'Class name resolution', + // Default values. If not specified will be the one used + 'prefix' => 'Humbug', + 'whitelist' => [], + ], + +// [ +// 'spec' => <<<'SPEC' +//Different kind of whitelisted class constant calls in the global scope: +//- do not prefix the use classes: they are all whitelisted +//- transforms the call into a FQ +//- resolve the aliases +//SPEC +// , +// 'whitelist' => ['Foo\Bar', 'Foo\Bar\Poz'], +// 'payload' => <<<'PHP' +// <<<'SPEC' +//Different kind of class constant calls in the global scope: +//- do not prefix the use classes: they are all whitelisted +//- transforms the call into a FQ +//- resolve the aliases +//SPEC +// , +// 'payload' => <<<'PHP' +// <<<'SPEC' +//Different kind of whitelisted class constant calls in a namespace: +//- do not prefix the use classes: they are all whitelisted +//- transforms the call into a FQ +//- resolve the aliases +//SPEC +// , +// 'whitelist' => [ +// 'Foo\Bar', +// 'Foo\Bar\Poz', +// +// 'A\Foo', +// 'A\Foo\Bar', +// 'A\Foo\Bar\Poz', +// 'A\Aoo', +// 'A\Aoo\Aoz', +// 'A\Aoz', +// 'A\Aoo\Aoz\Poz', +// ], +// 'payload' => <<<'PHP' +// <<<'SPEC' +//Different kind of class constant calls in a namespace: +//- do not prefix the use classes: they are all whitelisted +//- transforms the call into a FQ +//- resolve the aliases +//SPEC +// , +// 'payload' => <<<'PHP' +// <<<'SPEC' +Different kind of whitelisted class constant calls in multiple namespaces: +- do not prefix the use classes: they are all whitelisted +- transforms the call into a FQ +- resolve the aliases +SPEC + , + 'whitelist' => [ + 'Foo\Bar', + 'Foo\Bar\Poz', + + 'A\Foo', + 'A\Foo\Bar', + 'A\Foo\Bar\Poz', + 'A\Aoo', + 'A\Aoo\Aoz', + 'A\Aoz', + 'A\Aoo\Aoz\Poz', + + 'B\Foo', + 'B\Foo\Bar', + 'B\Foo\Bar\Poz', + 'B\Aoo', + 'B\Aoo\Aoz', + 'B\Aoz', + 'B\Aoo\Aoz\Poz', + ], + '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) -- resolve the alias +- do nothing SPEC , 'payload' => <<<'PHP' @@ -60,8 +59,7 @@ <<<'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) -- transform the call into a FQ call +- do nothing SPEC , 'payload' => <<<'PHP' @@ -106,8 +103,7 @@ <<<'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) +- 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 , 'payload' => <<<'PHP' @@ -82,8 +81,7 @@ <<<'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) +- prefix the class (cf. class belonging to the global scope tests and `scope.inc.php` for the built-in global + whitelisted classes) SPEC , 'payload' => <<<'PHP' @@ -104,8 +103,7 @@ hasAttribute(self::PARENT_ATTRIBUTE); + } + + public static function getParent(Node $node): Node + { + return $node->getAttribute(self::PARENT_ATTRIBUTE); + } + /** * @inheritdoc */ @@ -39,7 +53,7 @@ public function beforeTraverse(array $nodes) public function enterNode(Node $node): Node { if (!empty($this->stack)) { - $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + $node->setAttribute(self::PARENT_ATTRIBUTE, $this->stack[count($this->stack) - 1]); } $this->stack[] = $node; diff --git a/src/NodeVisitor/NamespaceStmtCollection.php b/src/NodeVisitor/Collection/NamespaceStmtCollection.php similarity index 65% rename from src/NodeVisitor/NamespaceStmtCollection.php rename to src/NodeVisitor/Collection/NamespaceStmtCollection.php index 4ed2f6c7..1fa8640e 100644 --- a/src/NodeVisitor/NamespaceStmtCollection.php +++ b/src/NodeVisitor/Collection/NamespaceStmtCollection.php @@ -12,12 +12,14 @@ * file that was distributed with this source code. */ -namespace Humbug\PhpScoper\NodeVisitor; +namespace Humbug\PhpScoper\NodeVisitor\Collection; use ArrayIterator; use Countable; +use Humbug\PhpScoper\NodeVisitor\AppendParentNode; use InvalidArgumentException; use IteratorAggregate; +use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Namespace_; use function Humbug\PhpScoper\deep_clone; @@ -34,19 +36,34 @@ public function add(Namespace_ $node) $this->nodes[] = deep_clone($node); } - public function getNamespaceName(): Name + public function findNamespaceForNode(Node $node): ?Name { if (0 === count($this->nodes)) { - throw new InvalidArgumentException('No name can be given: no namespace found.'); + return null; } if (1 < count($this->nodes)) { - throw new InvalidArgumentException('No name can be given: more than one namespace found.'); + return $this->getNodeNamespace($node); } return $this->nodes[0]->name; } + private function getNodeNamespace(Node $node): ?Name + { + if (false === AppendParentNode::hasParent($node)) { + return null; + } + + $parentNode = AppendParentNode::getParent($node); + + if ($parentNode instanceof Namespace_) { + return $parentNode->name; + } + + return $this->getNodeNamespace($parentNode); + } + /** * @inheritdoc */ diff --git a/src/NodeVisitor/UseStmtCollection.php b/src/NodeVisitor/Collection/UseStmtCollection.php similarity index 70% rename from src/NodeVisitor/UseStmtCollection.php rename to src/NodeVisitor/Collection/UseStmtCollection.php index 9b51f296..1bb7b2c0 100644 --- a/src/NodeVisitor/UseStmtCollection.php +++ b/src/NodeVisitor/Collection/UseStmtCollection.php @@ -12,7 +12,7 @@ * file that was distributed with this source code. */ -namespace Humbug\PhpScoper\NodeVisitor; +namespace Humbug\PhpScoper\NodeVisitor\Collection; use ArrayIterator; use IteratorAggregate; @@ -44,21 +44,29 @@ public function add(Use_ $node) * * will return the use statement for `Bar\Foo`. * - * @param string $name + * @param Name $node * * @return null|Name */ - public function findStatementForName(string $name): ?Name + public function findStatementForNode(Name $node): ?Name { + $name = $node->getFirst(); + foreach ($this->nodes as $use_) { foreach ($use_->uses as $useStatement) { if ($useStatement instanceof UseUse) { - if ($name === $useStatement->alias || $name === $useStatement->name->getLast()) { + if ($name === $useStatement->alias) { + // Match the alias return $useStatement->name; + } elseif (null !== $useStatement->alias) { + continue; } - } - //TODO + if ($name === $useStatement->name->getLast() && $useStatement->alias === null) { + // Match a simple use statement + return $useStatement->name; + } + } } } diff --git a/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php b/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php index 1be49755..f29584ed 100644 --- a/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php +++ b/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php @@ -14,8 +14,8 @@ namespace Humbug\PhpScoper\NodeVisitor\FunctionStmt; -use Humbug\PhpScoper\NodeVisitor\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; @@ -63,7 +63,7 @@ public function enterNode(Node $node): Node return $node; } - $useStatement = $this->useStatements->findStatementForName($node->getFirst()); + $useStatement = $this->useStatements->findStatementForNode($node->getFirst()); $prefix = false; @@ -73,7 +73,7 @@ public function enterNode(Node $node): Node $prefix = true; } else { - $namespaceStatement = $this->namespaceStatements->getNamespaceName(); + $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); $newNode = FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); } diff --git a/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php b/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php index 62f0487e..1669195e 100644 --- a/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php +++ b/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php @@ -14,8 +14,8 @@ namespace Humbug\PhpScoper\NodeVisitor\FunctionStmt; -use Humbug\PhpScoper\NodeVisitor\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; @@ -64,7 +64,7 @@ public function enterNode(Node $node): Node $x = ''; } - $useStatement = $this->useStmtCollection->findStatementForName($node->getFirst()); + $useStatement = $this->useStmtCollection->findStatementForNode($node->getFirst()); $prefix = false; @@ -74,7 +74,7 @@ public function enterNode(Node $node): Node $prefix = (false === in_array((string) $newNodeClass, $this->whitelist)); } else { - $namespaceStatement = $this->namespaceStatements->getNamespaceName(); + $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); $newNodeClass = FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); diff --git a/src/NodeVisitor/IgnoreNodeUtility.php b/src/NodeVisitor/IgnoreNodeUtility.php new file mode 100644 index 00000000..c6030517 --- /dev/null +++ b/src/NodeVisitor/IgnoreNodeUtility.php @@ -0,0 +1,29 @@ +hasAttribute(self::IGNORE_NODE_ATTRIBUTE) + && true === $node->getAttribute(self::IGNORE_NODE_ATTRIBUTE) + ); + } + + public static function ignoreNode(Node $node): void + { + $node->setAttribute(self::IGNORE_NODE_ATTRIBUTE, true); + } + + private function __construct() + { + } +} \ No newline at end of file diff --git a/src/NodeVisitor/IgnoreNamespaceScoperNodeVisitor.php b/src/NodeVisitor/IgnoreNodeVisitor.php similarity index 98% rename from src/NodeVisitor/IgnoreNamespaceScoperNodeVisitor.php rename to src/NodeVisitor/IgnoreNodeVisitor.php index 3e1667be..eb7c6f49 100644 --- a/src/NodeVisitor/IgnoreNamespaceScoperNodeVisitor.php +++ b/src/NodeVisitor/IgnoreNodeVisitor.php @@ -26,7 +26,7 @@ use PhpParser\Node\Stmt\UseUse; use PhpParser\NodeVisitorAbstract; -final class IgnoreNamespaceScoperNodeVisitor extends NodeVisitorAbstract +final class IgnoreNodeVisitor extends NodeVisitorAbstract { private $whitelist; private $whitelister; diff --git a/src/NodeVisitor/NameResolver.php b/src/NodeVisitor/NameResolver.php new file mode 100644 index 00000000..4ab69e19 --- /dev/null +++ b/src/NodeVisitor/NameResolver.php @@ -0,0 +1,106 @@ +, + * 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\NodeVisitor; + +use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; +use PhpParser\Node; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\Node\Stmt\TraitUse; +use PhpParser\NodeVisitorAbstract; + +final class NameResolver extends NodeVisitorAbstract +{ + private $prefix; + private $whitelist; + private $globalWhitelister; + private $nameResolver; + + /** + * @param string $prefix + * @param string[] $whitelist + * @param callable $globalWhitelister + * @param FullyQualifiedNameResolver $nameResolver + */ + public function __construct( + string $prefix, + array $whitelist, + callable $globalWhitelister, + FullyQualifiedNameResolver $nameResolver + ) { + $this->prefix = $prefix; + $this->whitelist = $whitelist; + $this->globalWhitelister = $globalWhitelister; + $this->nameResolver = $nameResolver; + } + + /** + * @inheritdoc + */ + public function enterNode(Node $node): Node + { + if (false === $node instanceof Name + || IgnoreNodeUtility::isNodeIgnored($node) + || false === AppendParentNode::hasParent($node) + ) { + return $node; + } + /** @var Name $node */ + + $parentNode = AppendParentNode::getParent($node); + + if ($parentNode instanceof Namespace_ + || $parentNode instanceof Class_ + || $parentNode instanceof TraitUse + || $parentNode instanceof Interface_ + || $parentNode instanceof Node\Stmt\UseUse + || $parentNode instanceof ConstFetch + || $parentNode instanceof Node\Stmt\TraitUseAdaptation\Precedence + || $parentNode instanceof Node\Stmt\TraitUseAdaptation\Alias + ) { + return $node; + } + +// if (false === ($parentNode instanceof ClassConstFetch) +// ) { +// return $node; +// } + + $resolvedNode = $this->nameResolver->resolveName($node); + + // Skip if is already prefixed + if ($this->prefix === $resolvedNode->getFirst()) { + return $resolvedNode; + } + + // Check if the class can be prefixed + if (1 === count($resolvedNode->parts) + && false === ($this->globalWhitelister)($resolvedNode->toString()) + ) { + return $resolvedNode; + } elseif (1 < count($resolvedNode->parts) + && in_array($resolvedNode->toString(), $this->whitelist) + ) { + return $resolvedNode; + } + + return FullyQualified::concat($this->prefix, $resolvedNode->toString(), $resolvedNode->getAttributes()); + } +} diff --git a/src/NodeVisitor/CollectNamespaceStmtNodeVisitor.php b/src/NodeVisitor/NamespaceStmtCollector.php similarity index 87% rename from src/NodeVisitor/CollectNamespaceStmtNodeVisitor.php rename to src/NodeVisitor/NamespaceStmtCollector.php index c459408d..76d3ac76 100644 --- a/src/NodeVisitor/CollectNamespaceStmtNodeVisitor.php +++ b/src/NodeVisitor/NamespaceStmtCollector.php @@ -14,11 +14,12 @@ namespace Humbug\PhpScoper\NodeVisitor; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use PhpParser\Node; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeVisitorAbstract; -final class CollectNamespaceStmtNodeVisitor extends NodeVisitorAbstract +final class NamespaceStmtCollector extends NodeVisitorAbstract { private $namespaceStatements; diff --git a/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php b/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php index 6c66fad6..417ee75c 100644 --- a/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php +++ b/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php @@ -14,8 +14,8 @@ namespace Humbug\PhpScoper\NodeVisitor\NewStmt; -use Humbug\PhpScoper\NodeVisitor\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Expr\New_; use PhpParser\Node\Name; @@ -52,23 +52,23 @@ public function enterNode(Node $node): Node $parentNode = $node->getAttribute('parent'); - if (false === ($parentNode instanceof New_)) { - return $node; - } - - if (1 === count($node->parts)) { - //TODO - $x = ''; - } +// if (false === ($parentNode instanceof New_)) { +// return $node; +// } +// +// if (1 === count($node->parts)) { +// //TODO +// $x = ''; +// } - $useStatement = $this->useStatements->findStatementForName($node->getFirst()); + $useStatement = $this->useStatements->findStatementForNode($node->getFirst()); if (null === $useStatement) { if (0 === count($this->namespaceStatements)) { return $node; } - $namespaceStatement = $this->namespaceStatements->getNamespaceName(); + $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); $newNode = FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); } else { diff --git a/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php b/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php index 8b350c86..2f7f7f52 100644 --- a/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php +++ b/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php @@ -14,8 +14,8 @@ namespace Humbug\PhpScoper\NodeVisitor\NewStmt; -use Humbug\PhpScoper\NodeVisitor\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Expr\New_; use PhpParser\Node\Name; @@ -61,14 +61,14 @@ public function enterNode(Node $node): Node return $node; } - $useStatement = $this->useStatements->findStatementForName($nodeClass->getFirst()); + $useStatement = $this->useStatements->findStatementForNode($nodeClass->getFirst()); if (null === $useStatement) { if (0 === count($this->namespaceStatements)) { return $node; } - $namespaceStatement = $this->namespaceStatements->getNamespaceName(); + $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); $newNodeClass = FullyQualified::concat($namespaceStatement, $nodeClass, $nodeClass->getAttributes()); } else { diff --git a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php new file mode 100644 index 00000000..f98e7339 --- /dev/null +++ b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php @@ -0,0 +1,49 @@ +namespaceStatements = $namespaceStatements; + $this->useStatements = $useStatements; + } + + /** + * Attempts to resolve the node name into a fully qualified node. Returns a valid name node on failure. + * + * @param Name $node + * + * @return Name|FullyQualified + */ + public function resolveName(Name $node): Name + { + if ($node instanceof FullyQualified) { + return $node; + } + + $useStatement = $this->useStatements->findStatementForNode($node); + + if (null !== $useStatement) { + return FullyQualified::concat($useStatement, $node->slice(1), $node->getAttributes()); + } + + $namespaceStatement = $this->namespaceStatements->findNamespaceForNode($node); + + if (null !== $namespaceStatement) { + return FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); + } + + return new FullyQualified($node, $node->getAttributes()); + } +} \ No newline at end of file diff --git a/src/NodeVisitor/ScopeConstStmtNodeVisitor.php b/src/NodeVisitor/ScopeConstStmtNodeVisitor.php index 75f81439..9b1db6f0 100644 --- a/src/NodeVisitor/ScopeConstStmtNodeVisitor.php +++ b/src/NodeVisitor/ScopeConstStmtNodeVisitor.php @@ -14,6 +14,8 @@ namespace Humbug\PhpScoper\NodeVisitor; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr\ClassConstFetch; @@ -60,7 +62,7 @@ public function enterNode(Node $node): Node return $node; } - $useStatement = $this->useStmtCollection->findStatementForName($constClassNode->getFirst()); + $useStatement = $this->useStmtCollection->findStatementForNode($constClassNode->getFirst()); $prefix = false; @@ -70,7 +72,7 @@ public function enterNode(Node $node): Node $prefix = true; } else { - $namespaceStatement = $this->namespaceStatements->getNamespaceName(); + $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); $newClassNode = FullyQualified::concat($namespaceStatement, $constClassNode, $constClassNode->getAttributes()); } diff --git a/src/NodeVisitor/UseStmt/ScopeUseStmtNodeVisitor.php b/src/NodeVisitor/UseStmt/ScopeUseStmtNodeVisitor.php deleted file mode 100644 index 23923385..00000000 --- a/src/NodeVisitor/UseStmt/ScopeUseStmtNodeVisitor.php +++ /dev/null @@ -1,166 +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\NodeVisitor\UseStmt; - -use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; -use PhpParser\Node; -use PhpParser\Node\Name; -use PhpParser\Node\Stmt\GroupUse; -use PhpParser\Node\Stmt\Use_; -use PhpParser\Node\Stmt\UseUse; -use PhpParser\NodeTraverser; -use PhpParser\NodeVisitorAbstract; - -/** - * Manipulates use statements. - */ -final class ScopeUseStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $whitelist; - private $whitelistedStatements; - - /** - * @param string $prefix - * @param string[] $whitelist - * @param WhitelistedStatements $whitelistedStatements - */ - public function __construct(string $prefix, array $whitelist, WhitelistedStatements $whitelistedStatements) - { - $this->prefix = $prefix; - $this->whitelist = $whitelist; - $this->whitelistedStatements = $whitelistedStatements; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node) - { - if (false === ($node instanceof UseUse)) { - return $node; - } - - /** @var UseUse $node */ - if (Use_::TYPE_UNKNOWN === $node->type) { - $nodeType = $node->getAttribute('parent')->type; - } else { - $nodeType = $node->type; - } - - // Mark use statements of whitelisted classes - if (Use_::TYPE_NORMAL === $nodeType && in_array((string) $node->name, $this->whitelist) - ) { - $this->whitelistedStatements->addNode($node); - - return $node; - } - - if ($node->hasAttribute('parent') - && false === ($node->getAttribute('parent') instanceof GroupUse) - && $this->prefix !== $node->name->getFirst() - // Is not an ignored use statement - && false === ( - $node->hasAttribute('phpscoper_ignore') - && true === $node->getAttribute('phpscoper_ignore') - ) - ) { - $node->name = Name::concat($this->prefix, $node->name); - } - - return $node; - } - - /** - * Removes use statements of whitelisted classes. - * - * {@inheritdoc} - */ - public function leaveNode(Node $node) - { - if ($node instanceof Use_ && 0 === count($node->uses)) { - return NodeTraverser::REMOVE_NODE; - } - - if (false === ($node instanceof UseUse)) { - return $node; - } - - if ($this->whitelistedStatements->has($node)) { - return NodeTraverser::REMOVE_NODE; - } - - return $node; - } - -// /** -// * @var string -// */ -// private $prefix; -// -// /** -// * @var array -// */ -// private $aliases; -// -// public function __construct(string $prefix) -// { -// $this->prefix = $prefix; -// } -// -// /** -// * @inheritdoc -// */ -// public function beforeTraverse(array $nodes) -// { -// $this->aliases = []; -// } -// -// /** -// * @inheritdoc -// */ -// public function enterNode(Node $node) -// { -// /* Collate all single level aliases */ -// if ($node instanceof UseUse -// && (!$node->hasAttribute('parent') -// || false === ($node->getAttribute('parent') instanceof GroupUse)) -// && $this->prefix !== $node->name->getFirst() -// && 1 === count($node->name->parts) -// && $node->alias !== $node->name->getFirst() -// ) { -// $this->aliases[$node->alias] = $node; -// -// return; -// } -// -// $this->scopeUseStmtIfUsedInAnyNameAsAliasedNamespace($node); -// } -// -// /** -// * @param Node $node -// */ -// private function scopeUseStmtIfUsedInAnyNameAsAliasedNamespace(Node $node) -// { -// if ($node instanceof Name -// && 1 < count($node->parts) -// && in_array($node->getFirst(), array_keys($this->aliases)) -// ) { -// $nodeToPrefix = $this->aliases[$node->getFirst()]; -// $nodeToPrefix->name = Name::concat($this->prefix, $nodeToPrefix->name); -// unset($this->aliases[$node->getFirst()]); -// } -// } -} diff --git a/src/NodeVisitor/UseStmt/CollectUseStmtNodeVisitor.php b/src/NodeVisitor/UseStmt/UseStmtCollector.php similarity index 87% rename from src/NodeVisitor/UseStmt/CollectUseStmtNodeVisitor.php rename to src/NodeVisitor/UseStmt/UseStmtCollector.php index 1d12fe6e..b15abe5e 100644 --- a/src/NodeVisitor/UseStmt/CollectUseStmtNodeVisitor.php +++ b/src/NodeVisitor/UseStmt/UseStmtCollector.php @@ -14,12 +14,12 @@ namespace Humbug\PhpScoper\NodeVisitor\UseStmt; -use Humbug\PhpScoper\NodeVisitor\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Stmt\Use_; use PhpParser\NodeVisitorAbstract; -final class CollectUseStmtNodeVisitor extends NodeVisitorAbstract +final class UseStmtCollector extends NodeVisitorAbstract { private $useStatements; diff --git a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php new file mode 100644 index 00000000..44cf00f4 --- /dev/null +++ b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php @@ -0,0 +1,107 @@ +, + * 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\NodeVisitor\UseStmt; + +use Humbug\PhpScoper\NodeVisitor\AppendParentNode; +use Humbug\PhpScoper\NodeVisitor\IgnoreNodeUtility; +use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt\GroupUse; +use PhpParser\Node\Stmt\Use_; +use PhpParser\Node\Stmt\UseUse; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +/** + * Manipulates use statements. + */ +final class UseStmtPrefixer extends NodeVisitorAbstract +{ + private $prefix; + private $whitelist; + private $whitelistedStatements; + + /** + * @param string $prefix + * @param string[] $whitelist + * @param WhitelistedStatements $whitelistedStatements + */ + public function __construct(string $prefix, array $whitelist, WhitelistedStatements $whitelistedStatements) + { + $this->prefix = $prefix; + $this->whitelist = $whitelist; + $this->whitelistedStatements = $whitelistedStatements; + } + + /** + * @inheritdoc + */ + public function enterNode(Node $node) + { + if (false === ($node instanceof UseUse)) { + return $node; + } + + /** @var UseUse $node */ + if (Use_::TYPE_UNKNOWN === $node->type) { + $nodeType = AppendParentNode::getParent($node)->type; + } else { + $nodeType = $node->type; + } + + // Mark use statements of whitelisted classes + if (Use_::TYPE_NORMAL === $nodeType && in_array((string) $node->name, $this->whitelist) + ) { + $this->whitelistedStatements->addNode($node); + + return $node; + } + + if (AppendParentNode::hasParent($node) + && false === (AppendParentNode::getParent($node) instanceof GroupUse) + // The prefix is not already applied + && $this->prefix !== $node->name->getFirst() + // Is not an ignored use statement + && false === IgnoreNodeUtility::isNodeIgnored($node) + ) { + $node->name = Name::concat($this->prefix, $node->name); + } + + return $node; + } +// +// /** +// * Removes use statements of whitelisted classes. +// * +// * {@inheritdoc} +// */ +// public function leaveNode(Node $node) +// { +// if ($node instanceof Use_ && 0 === count($node->uses)) { +// return NodeTraverser::REMOVE_NODE; +// } +// +// if (false === ($node instanceof UseUse)) { +// return $node; +// } +// +// if ($this->whitelistedStatements->has($node)) { +// return NodeTraverser::REMOVE_NODE; +// } +// +// return $node; +// } +} diff --git a/src/Scoper/PhpScoper.php b/src/Scoper/PhpScoper.php index c3b741c0..5b600abe 100644 --- a/src/Scoper/PhpScoper.php +++ b/src/Scoper/PhpScoper.php @@ -32,11 +32,11 @@ final class PhpScoper implements Scoper private $decoratedScoper; private $traverserFactory; - public function __construct(Parser $parser, Scoper $decoratedScoper) + public function __construct(Parser $parser, Scoper $decoratedScoper, TraverserFactory $traverserFactory) { $this->parser = $parser; $this->decoratedScoper = $decoratedScoper; - $this->traverserFactory = new TraverserFactory(); + $this->traverserFactory = $traverserFactory; } /** diff --git a/src/Scoper/TraverserFactory.php b/src/Scoper/TraverserFactory.php index bd710fb2..d3d080c7 100644 --- a/src/Scoper/TraverserFactory.php +++ b/src/Scoper/TraverserFactory.php @@ -15,60 +15,21 @@ namespace Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\NodeVisitor; -use Humbug\PhpScoper\NodeVisitor\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; use PhpParser\NodeTraverser; use PhpParser\NodeTraverserInterface; -/** - * @internal - */ -final class TraverserFactory +interface TraverserFactory { /** - * Functions for which the arguments will be prefixed. + * @param string $prefix Prefix to apply to the files. + * @param string[] $whitelist List of classes to exclude from the scoping. + * @param callable $globalWhitelister 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. + * + * @return NodeTraverserInterface */ - const WHITELISTED_FUNCTIONS = [ - 'class_exists', - 'interface_exists', - ]; - - private $traverser; - - public function create(string $prefix, array $whitelist, callable $globalWhitelister): NodeTraverserInterface - { - if (null !== $this->traverser) { - return $this->traverser; - } - - $this->traverser = new NodeTraverser(); - - $namespaceStatements = new NodeVisitor\NamespaceStmtCollection(); - $useStatements = new UseStmtCollection(); - $whitelistedStatements = new WhitelistedStatements(); - - $this->traverser->addVisitor(new NodeVisitor\ParentNodeVisitor()); - - $this->traverser->addVisitor(new NodeVisitor\CollectNamespaceStmtNodeVisitor($namespaceStatements)); - $this->traverser->addVisitor(new NodeVisitor\IgnoreNamespaceScoperNodeVisitor($whitelist, $globalWhitelister)); - $this->traverser->addVisitor(new NodeVisitor\ScopeNamespaceStmtNodeVisitor($prefix)); - - $this->traverser->addVisitor(new NodeVisitor\UseStmt\CollectUseStmtNodeVisitor($useStatements)); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeUseStmtNodeVisitor($prefix, $whitelist, $whitelistedStatements)); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeSingleLevelUseAliasVisitor($prefix)); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeGroupUseStmtNodeVisitor($prefix)); - - $this->traverser->addVisitor(new NodeVisitor\ScopeFullyQualifiedNodeVisitor($prefix)); - $this->traverser->addVisitor(new NodeVisitor\ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor($prefix, $globalWhitelister)); - $this->traverser->addVisitor(new NodeVisitor\ScopeConstStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - - $this->traverser->addVisitor(new NodeVisitor\NewStmt\ScopeNewStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - $this->traverser->addVisitor(new NodeVisitor\NewStmt\ScopeSingleLevelNewStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - - $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeFunctionCallArgumentsStmtNodeVisitor($prefix, $whitelist, self::WHITELISTED_FUNCTIONS)); - $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeStaticCallStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeFunctionCallStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - - return $this->traverser; - } + public function create(string $prefix, array $whitelist, callable $globalWhitelister): NodeTraverserInterface; } diff --git a/src/Scoper/TraverserFactory/NativeTraverserFactory.php b/src/Scoper/TraverserFactory/NativeTraverserFactory.php new file mode 100644 index 00000000..e5bc0990 --- /dev/null +++ b/src/Scoper/TraverserFactory/NativeTraverserFactory.php @@ -0,0 +1,81 @@ +, + * 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\Scoper\TraverserFactory; + +use Humbug\PhpScoper\NodeVisitor; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; +use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; +use Humbug\PhpScoper\Scoper\TraverserFactory; +use PhpParser\NodeTraverser; +use PhpParser\NodeTraverserInterface; + +final class NativeTraverserFactory implements TraverserFactory +{ + /** + * Functions for which the arguments will be prefixed. + */ + const WHITELISTED_FUNCTIONS = [ + 'class_exists', + 'interface_exists', + ]; + + private $traverser; + + /** + * @inheritdoc + */ + public function create(string $prefix, array $whitelist, callable $globalWhitelister): NodeTraverserInterface + { + if (null !== $this->traverser) { + return $this->traverser; + } + + $this->traverser = new NodeTraverser(); + + $namespaceStatements = new NamespaceStmtCollection(); + $useStatements = new UseStmtCollection(); + $whitelistedStatements = new WhitelistedStatements(); + + $nameResolver = new FullyQualifiedNameResolver($namespaceStatements, $useStatements); + + $this->traverser->addVisitor(new NodeVisitor\AppendParentNode()); + $this->traverser->addVisitor(new NodeVisitor\IgnoreNodeVisitor($whitelist, $globalWhitelister)); + + $this->traverser->addVisitor(new NodeVisitor\NamespaceStmtCollector($namespaceStatements)); + $this->traverser->addVisitor(new NodeVisitor\ScopeNamespaceStmtNodeVisitor($prefix)); + + $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($useStatements)); + $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $whitelistedStatements)); +// $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeSingleLevelUseAliasVisitor($prefix)); +// $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeGroupUseStmtNodeVisitor($prefix)); + + $this->traverser->addVisitor(new NodeVisitor\NameResolver($prefix, $whitelist, $globalWhitelister, $nameResolver)); + +// $this->traverser->addVisitor(new NodeVisitor\ScopeFullyQualifiedNodeVisitor($prefix)); +// $this->traverser->addVisitor(new NodeVisitor\ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor($prefix, $globalWhitelister)); +// $this->traverser->addVisitor(new NodeVisitor\ScopeConstStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); + +// $this->traverser->addVisitor(new NodeVisitor\NewStmt\ScopeNewStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); +// $this->traverser->addVisitor(new NodeVisitor\NewStmt\ScopeSingleLevelNewStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); + +// $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeFunctionCallArgumentsStmtNodeVisitor($prefix, $whitelist, self::WHITELISTED_FUNCTIONS)); +// $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeStaticCallStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); +// $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeFunctionCallStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); + + return $this->traverser; + } +} diff --git a/src/functions.php b/src/functions.php index 6299f1ad..aad3da3d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -21,6 +21,7 @@ use Humbug\PhpScoper\Handler\HandleAddPrefix; use Humbug\PhpScoper\Scoper\Composer\InstalledPackagesScoper; use Humbug\PhpScoper\Scoper\Composer\JsonFileScoper; +use Humbug\PhpScoper\Scoper\TraverserFactory\NativeTraverserFactory; use Humbug\PhpScoper\Scoper\NullScoper; use Humbug\PhpScoper\Scoper\PatchScoper; use Humbug\PhpScoper\Scoper\PhpScoper; @@ -99,7 +100,8 @@ function create_scoper(): Scoper new InstalledPackagesScoper( new PhpScoper( create_parser(), - new NullScoper() + new NullScoper(), + new NativeTraverserFactory() ) ) ) diff --git a/tests/NodeVisitor/Resolver/NameResolverTest.php b/tests/NodeVisitor/Resolver/NameResolverTest.php new file mode 100644 index 00000000..30fe3bfa --- /dev/null +++ b/tests/NodeVisitor/Resolver/NameResolverTest.php @@ -0,0 +1,194 @@ +addVisitor(new AppendParentNode()); + $traverser->addVisitor(new NamespaceStmtCollector($namespaceStatements)); + $traverser->addVisitor(new UseStmtCollector($useStatements)); + + return $traverser; + } + }; + + self::$createScoper = function () use ($nodeTraverserFactory): PhpScoper { + return new PhpScoper( + create_parser(), + new FakeScoper(), + $nodeTraverserFactory + ); + }; + } + + /** + * @inheritdoc + */ + public function setUp() + { + $this->scoper = self::$createScoper(); + + if (null === $this->tmp) { + $this->cwd = getcwd(); + $this->tmp = make_tmp_dir('scoper', __CLASS__); + } + + chdir($this->tmp); + } + /** + * @inheritdoc + */ + public function tearDown() + { + chdir($this->cwd); + remove_dir($this->tmp); + } + + /** + * @dataProvider provideFiles + */ + public function test_can_scope_valid_files(string $content, string $prefix, string $expected) + { + $filePath = escape_path($this->tmp.'/file.php'); + touch($filePath); + file_put_contents($filePath, $content); + + $patchers = [create_fake_patcher()]; + + $whitelist = []; + + $whitelister = function (string $className) { + return 'AppKernel' === $className; + }; + + $actual = $this->scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister); + + $this->assertSame($expected, $actual); + } + + public function provideFiles() + { + // + // Namespace declaration + // + // ============================ + yield '[Namespace declaration] no declaration' => [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' +scoper = new PhpScoper( create_parser(), - new FakeScoper() + new FakeScoper(), + new NativeTraverserFactory() ); if (null === $this->tmp) { @@ -141,7 +145,8 @@ public function test_does_not_scope_file_if_is_not_a_PHP_file() $scoper = new PhpScoper( new FakeParser(), - $this->decoratedScoper + $this->decoratedScoper, + new NativeTraverserFactory() ); $actual = $scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister); @@ -214,7 +219,8 @@ public function test_does_not_scope_a_non_PHP_binary_files() $scoper = new PhpScoper( new FakeParser(), - $this->decoratedScoper + $this->decoratedScoper, + new NativeTraverserFactory() ); $actual = $scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister); @@ -274,27 +280,53 @@ public function test_can_scope_valid_files(string $spec, string $content, string $actual = $this->scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister); - $titleSeparator = str_repeat('=', strlen($spec)); + $titleSeparator = str_repeat( + '=', + min( + strlen($spec), + 80 + ) + ); $this->assertSame( $expected, $actual, <<files()->in(self::SPECS_PATH); + if (file_exists(self::SECONDARY_SPECS_PATH)) { + $files = (new Finder())->files()->in(self::SECONDARY_SPECS_PATH); + + if (0 === count($files)) { + $files = (new Finder())->files()->in(self::SPECS_PATH); + } + } else { + $files = (new Finder())->files()->in(self::SPECS_PATH); + } foreach ($files as $file) { try { From c6689f4e85611f8b97128b082eb41fea8f08cac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Wed, 6 Sep 2017 10:10:12 +0100 Subject: [PATCH 3/9] Make some progress --- _specs/class-FQ.php | 383 ----------------- specs/class-FQ.php | 384 ++++++++++++++++++ ...-level-with-single-level-use-and-alias.php | 12 +- ...-scope-two-level-with-single-level-use.php | 1 - ...h-single-level-use-statement-and-alias.php | 7 +- .../namespace-scope-single-level.php | 10 +- ...-level-with-single-level-use-and-alias.php | 40 +- ...-scope-two-level-with-single-level-use.php | 5 - .../class-const/namespace-scope-two-level.php | 8 +- ...e-part-with-single-level-use-statement.php | 8 +- specs/class/conditional.php | 2 +- ...h-single-level-use-statement-and-alias.php | 12 +- ...global-with-single-level-use-statement.php | 12 +- ...namespaced-with-single-level-use-alias.php | 3 - ...-part-namespaced-with-single-level-use.php | 3 - ...spaced-with-single-level-use-and-alias.php | 93 ++--- ...parts-namespaced-with-single-level-use.php | 3 - ...h-single-level-use-statement-and-alias.php | 12 +- ...global-with-single-level-use-statement.php | 12 +- .../namespace-single-part-namespaced.php | 4 +- specs/func-declaration/global.php | 33 +- specs/func-declaration/namespace.php | 16 +- specs/func-params.php | 16 +- ...h-single-level-use-statement-and-alias.php | 2 - ...l-func-with-single-level-use-statement.php | 2 - ...d-func-with-single-level-use-and-alias.php | 5 +- ...-namespaced-func-with-single-level-use.php | 3 - ...h-single-level-use-statement-and-alias.php | 2 - ...l-func-with-single-level-use-statement.php | 2 - ...h-single-level-use-statement-and-alias.php | 4 - ...e-part-with-single-level-use-statement.php | 4 - ...-parts-with-single-level-use-and-alias.php | 16 +- ...-scope-two-parts-with-single-level-use.php | 12 +- ...e-part-with-single-level-use-statement.php | 4 - ...espace-two-parts-with-single-level-use.php | 6 +- ...namespace-two-parts-with-two-level-use.php | 4 - specs/new/namespace-two-parts.php | 2 +- ...h-single-level-use-statement-and-alias.php | 4 - ...e-part-with-single-level-use-statement.php | 4 - ...-parts-with-single-level-use-and-alias.php | 16 +- ...-scope-two-parts-with-single-level-use.php | 12 +- ...e-part-with-single-level-use-statement.php | 4 - ...espace-two-parts-with-single-level-use.php | 6 +- ...namespace-two-parts-with-two-level-use.php | 4 - specs/static-method/namespace-two-parts.php | 2 +- specs/use/use-class-alias.php | 8 +- specs/use/use-class-group.php | 30 +- specs/use/use-class.php | 6 +- specs/use/use-const-group.php | 10 +- specs/use/use-func-const.php | 39 -- specs/use/use-func-group.php | 8 +- specs/use/use-mix-group.php | 4 +- .../Collection/NamespaceStmtCollection.php | 25 +- .../Collection/UseStmtCollection.php | 22 +- .../Ignore/UseIgnoreNodeVisitor.php | 118 ++++++ src/NodeVisitor/IgnoreNodeVisitor.php | 6 +- ...{NameResolver.php => NameStmtPrefixer.php} | 47 ++- ...eVisitor.php => NamespaceStmtPrefixer.php} | 26 +- .../Resolver/FullyQualifiedNameResolver.php | 42 +- src/NodeVisitor/Resolver/ResolvedValue.php | 35 ++ src/NodeVisitor/StringScalarPrefixer.php | 99 +++++ .../UseStmt/GroupUseStmtTransformer.php | 88 ++++ .../UseStmt/ScopeGroupUseStmtNodeVisitor.php | 57 --- src/NodeVisitor/UseStmt/UseStmtCollector.php | 9 +- src/NodeVisitor/UseStmt/UseStmtPrefixer.php | 1 - .../NativeTraverserFactory.php | 11 +- tests/Scoper/PhpScoperTest.php | 9 +- 67 files changed, 1036 insertions(+), 863 deletions(-) delete mode 100644 _specs/class-FQ.php create mode 100644 specs/class-FQ.php delete mode 100644 specs/use/use-func-const.php create mode 100644 src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php rename src/NodeVisitor/{NameResolver.php => NameStmtPrefixer.php} (64%) rename src/NodeVisitor/{ScopeNamespaceStmtNodeVisitor.php => NamespaceStmtPrefixer.php} (53%) create mode 100644 src/NodeVisitor/Resolver/ResolvedValue.php create mode 100644 src/NodeVisitor/StringScalarPrefixer.php create mode 100644 src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php delete mode 100644 src/NodeVisitor/UseStmt/ScopeGroupUseStmtNodeVisitor.php diff --git a/_specs/class-FQ.php b/_specs/class-FQ.php deleted file mode 100644 index 68044596..00000000 --- a/_specs/class-FQ.php +++ /dev/null @@ -1,383 +0,0 @@ -, - * 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' => 'Class name resolution', - // Default values. If not specified will be the one used - 'prefix' => 'Humbug', - 'whitelist' => [], - ], - -// [ -// 'spec' => <<<'SPEC' -//Different kind of whitelisted class constant calls in the global scope: -//- do not prefix the use classes: they are all whitelisted -//- transforms the call into a FQ -//- resolve the aliases -//SPEC -// , -// 'whitelist' => ['Foo\Bar', 'Foo\Bar\Poz'], -// 'payload' => <<<'PHP' -// <<<'SPEC' -//Different kind of class constant calls in the global scope: -//- do not prefix the use classes: they are all whitelisted -//- transforms the call into a FQ -//- resolve the aliases -//SPEC -// , -// 'payload' => <<<'PHP' -// <<<'SPEC' -//Different kind of whitelisted class constant calls in a namespace: -//- do not prefix the use classes: they are all whitelisted -//- transforms the call into a FQ -//- resolve the aliases -//SPEC -// , -// 'whitelist' => [ -// 'Foo\Bar', -// 'Foo\Bar\Poz', -// -// 'A\Foo', -// 'A\Foo\Bar', -// 'A\Foo\Bar\Poz', -// 'A\Aoo', -// 'A\Aoo\Aoz', -// 'A\Aoz', -// 'A\Aoo\Aoz\Poz', -// ], -// 'payload' => <<<'PHP' -// <<<'SPEC' -//Different kind of class constant calls in a namespace: -//- do not prefix the use classes: they are all whitelisted -//- transforms the call into a FQ -//- resolve the aliases -//SPEC -// , -// 'payload' => <<<'PHP' -// <<<'SPEC' -Different kind of whitelisted class constant calls in multiple namespaces: -- do not prefix the use classes: they are all whitelisted -- transforms the call into a FQ -- resolve the aliases -SPEC - , - 'whitelist' => [ - 'Foo\Bar', - 'Foo\Bar\Poz', - - 'A\Foo', - 'A\Foo\Bar', - 'A\Foo\Bar\Poz', - 'A\Aoo', - 'A\Aoo\Aoz', - 'A\Aoz', - 'A\Aoo\Aoz\Poz', - - 'B\Foo', - 'B\Foo\Bar', - 'B\Foo\Bar\Poz', - 'B\Aoo', - 'B\Aoo\Aoz', - 'B\Aoz', - 'B\Aoo\Aoz\Poz', - ], - 'payload' => <<<'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' => 'Class name resolution', + // Default values. If not specified will be the one used + 'prefix' => 'Humbug', + 'whitelist' => [], + ], + + [ + 'spec' => <<<'SPEC' +Different kind of whitelisted class constant calls in the global scope: +- do not prefix the use classes: they are all whitelisted +- transforms the call into a FQ +- resolve the aliases +SPEC + , + 'whitelist' => ['Foo\Bar', 'Foo\Bar\Poz'], + 'payload' => <<<'PHP' + <<<'SPEC' +Different kind of class constant calls in the global scope: +- do not prefix the use classes: they are all whitelisted +- transforms the call into a FQ +- resolve the aliases +SPEC + , + 'payload' => <<<'PHP' + <<<'SPEC' +Different kind of whitelisted class constant calls in a namespace: +- do not prefix the use classes: they are all whitelisted +- transforms the call into a FQ +- resolve the aliases +SPEC + , + 'whitelist' => [ + 'Foo\Bar', + 'Foo\Bar\Poz', + + 'A\Foo', + 'A\Foo\Bar', + 'A\Foo\Bar\Poz', + 'A\Aoo', + 'A\Aoo\Aoz', + 'A\Aoz', + 'A\Aoo\Aoz\Poz', + ], + 'payload' => <<<'PHP' + <<<'SPEC' +Different kind of class constant calls in a namespace: +- do not prefix the use classes: they are all whitelisted +- transforms the call into a FQ +- resolve the aliases +SPEC + , + 'payload' => <<<'PHP' + <<<'SPEC' +Different kind of whitelisted class constant calls in multiple namespaces: +- do not prefix the use classes: they are all whitelisted +- transforms the call into a FQ +- resolve the aliases +SPEC + , + 'whitelist' => [ + 'Foo\Bar', + 'Foo\Bar\Poz', + + 'A\Foo', + 'A\Foo\Bar', + 'A\Foo\Bar\Poz', + 'A\Aoo', + 'A\Aoo\Aoz', + 'A\Aoz', + 'A\Aoo\Aoz\Poz', + + 'B\Foo', + 'B\Foo\Bar', + 'B\Foo\Bar\Poz', + 'B\Aoo', + 'B\Aoo\Aoz', + 'B\Aoz', + 'B\Aoo\Aoz\Poz', + ], + 'payload' => <<<'PHP' + <<<'SPEC' FQ constant call on a namespaced class 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 +- prefix the class only (not the use statement, cf. tests related to classes belonging to the global namespace) SPEC , 'payload' => <<<'PHP' @@ -81,7 +80,7 @@ <<<'SPEC' FQ constant call on a whitelisted namespaced class 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 +- prefix the class only (not the use statement, cf. tests related to classes belonging to the global namespace) SPEC , 'whitelist' => ['Foo\Bar'], @@ -127,7 +125,7 @@ <<<'PHP' @@ -69,7 +68,7 @@ namespace Humbug\A; use Foo as X; -\Foo::MAIN_CONST; +\X::MAIN_CONST; PHP ], @@ -106,7 +105,7 @@ 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) -- transform the call into a FQ call +- do not touch the call SPEC , 'payload' => <<<'PHP' @@ -123,7 +122,7 @@ namespace Humbug\A; use Humbug\AppKernel as X; -\Humbug\AppKernel::MAIN_CONST; +\X::MAIN_CONST; PHP ], diff --git a/specs/class-const/namespace-scope-single-level.php b/specs/class-const/namespace-scope-single-level.php index 4db670ca..b15f9a61 100644 --- a/specs/class-const/namespace-scope-single-level.php +++ b/specs/class-const/namespace-scope-single-level.php @@ -24,7 +24,7 @@ 'spec' => <<<'SPEC' Constant call on a class belonging to the global namespace or the current namespace: - prefix the namespace -- do not touch the call +- transform the call into a FQ call SPEC , 'payload' => <<<'PHP' @@ -37,7 +37,8 @@ <<<'SPEC' Constant call on a whitelisted class belonging 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) - transforms the call into a FQ call to avoid autoloading issues SPEC , @@ -82,7 +83,8 @@ <<<'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) -- transforms the call into a FQ call to avoid autoloading issues +- prefix the class only (not the use statement, cf. tests related to classes from the global scope) SPEC , 'payload' => <<<'PHP' @@ -98,8 +95,7 @@ namespace Humbug\A; use Foo as X; - -\Humbug\Foo\Bar::MAIN_CONST; +\Humbug\X\Bar::MAIN_CONST; PHP ], @@ -127,36 +123,6 @@ namespace Humbug\A; use Foo as X; - -\Foo\Bar::MAIN_CONST; - -PHP - ], - - [ - 'spec' => <<<'SPEC' -FQ constant call on a whitelisted namespaced class 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 - , - 'whitelist' => ['Foo\Bar'], - 'payload' => <<<'PHP' - ['PHPUnit\Command'], + 'whitelist' => ['X\PHPUnit\Command'], 'payload' => <<<'PHP' [], ], - // As it is extremely rare to use a `use constant` statement for a built-in constant from the + // As it is extremely rare to use a `use const` statement for a built-in constant from the // global scope, we can relatively safely assume it is a user-land declared constant which should // be prefixed. @@ -35,14 +35,13 @@ 'payload' => <<<'PHP' <<<'PHP' [], ], - // As it is extremely rare to use a `use constant` statement for a built-in constant from the + // As it is extremely rare to use a `use const` statement for a built-in constant from the // global scope, we can relatively safely assume it is a user-land declared constant which should // be prefixed. @@ -35,14 +35,13 @@ 'payload' => <<<'PHP' <<<'PHP' [], ], - [ - 'spec' => <<<'SPEC' -Namespaced constant call with namespace partially imported -- do not prefix the use statement (cf. tests related to global classes) -- prefix the call -- transform the call in a FQ call -SPEC - , - 'payload' => <<<'PHP' - <<<'SPEC' +//Namespaced constant call with namespace partially imported +//- do not prefix the use statement (cf. tests related to global classes) +//- prefix the call +//- transform the call in a FQ call +//SPEC +// , +// 'payload' => <<<'PHP' +// <<<'SPEC' @@ -61,34 +60,32 @@ <<<'SPEC' -Namespaced constant call with namespace partially imported -- do not prefix the use statement (cf. tests related to global classes) -- prefix the call -- transform the call in a FQ call -SPEC - , - 'whitelist' => ['Foo\Bar\DUMMY_CONST'], - 'payload' => <<<'PHP' - <<<'SPEC' +//Namespaced constant call with namespace partially imported +//- do not prefix the use statement (cf. tests related to global classes) +//- prefix the call +//- transform the call in a FQ call +//SPEC +// , +// 'whitelist' => ['Foo\Bar\DUMMY_CONST'], +// 'payload' => <<<'PHP' +// [], ], - // As it is extremely rare to use a `use constant` statement for a built-in constant from the + // As it is extremely rare to use a `use const` statement for a built-in constant from the // global scope, we can relatively safely assume it is a user-land declared constant which should // be prefixed. @@ -38,7 +38,7 @@ namespace A; -use constant DUMMY_CONST as FOO; +use const DUMMY_CONST as FOO; FOO; ---- @@ -46,8 +46,7 @@ namespace Humbug\A; -use constant Humbug\DUMMY_CONST as FOO; - +use const Humbug\DUMMY_CONST as FOO; \Humbug\DUMMY_CONST; PHP @@ -65,7 +64,7 @@ namespace A; -use constant DUMMY_CONST as FOO; +use const DUMMY_CONST as FOO; \FOO; ---- @@ -73,8 +72,7 @@ namespace Humbug\A; -use constant Humbug\DUMMY_CONST as FOO; - +use const Humbug\DUMMY_CONST as FOO; \FOO; PHP diff --git a/specs/const/namespace-global-with-single-level-use-statement.php b/specs/const/namespace-global-with-single-level-use-statement.php index ecb32fa3..476b5e9a 100644 --- a/specs/const/namespace-global-with-single-level-use-statement.php +++ b/specs/const/namespace-global-with-single-level-use-statement.php @@ -20,7 +20,7 @@ 'whitelist' => [], ], - // As it is extremely rare to use a `use constant` statement for a built-in constant from the + // As it is extremely rare to use a `use const` statement for a built-in constant from the // global scope, we can relatively safely assume it is a user-land declared constant which should // be prefixed. @@ -38,7 +38,7 @@ namespace A; -use constant DUMMY_CONST; +use const DUMMY_CONST; DUMMY_CONST; ---- @@ -46,8 +46,7 @@ namespace Humbug\A; -use constant Humbug\DUMMY_CONST; - +use const Humbug\DUMMY_CONST; \Humbug\DUMMY_CONST; PHP @@ -65,7 +64,7 @@ namespace A; -use constant DUMMY_CONST; +use const DUMMY_CONST; \DUMMY_CONST; ---- @@ -73,8 +72,7 @@ namespace Humbug\A; -use constant Humbug\DUMMY_CONST; - +use const Humbug\DUMMY_CONST; \DUMMY_CONST; PHP diff --git a/specs/const/namespace-single-part-namespaced.php b/specs/const/namespace-single-part-namespaced.php index f71b6079..5415dbbe 100644 --- a/specs/const/namespace-single-part-namespaced.php +++ b/specs/const/namespace-single-part-namespaced.php @@ -39,7 +39,7 @@ namespace Humbug\A; -\Humbug\PHPUnit\DUMMY_CONST; +\Humbug\A\PHPUnit\DUMMY_CONST; PHP ], @@ -86,7 +86,7 @@ namespace Humbug\A; -\Humbug\PHPUnit\DUMMY_CONST; +\Humbug\A\PHPUnit\DUMMY_CONST; PHP ], diff --git a/specs/func-declaration/global.php b/specs/func-declaration/global.php index 7c645986..c2fae8bc 100644 --- a/specs/func-declaration/global.php +++ b/specs/func-declaration/global.php @@ -39,21 +39,15 @@ function foo( \AppKernel $arg5, X\Y $arg6, \X\Y $arg7 -); +) { +} ---- <<<'SPEC' FQ new statement call of a whitelisted namespaced class partially imported with an aliased use statement: - do not touch the use statement: see tests for the use statements as to why -- do not prefix the call +- prefix the call: as it is a FQ the use statement is ignored SPEC , 'whitelist' => ['Foo\Bar'], @@ -182,8 +176,7 @@ <<<'SPEC' FQ static method call statement of a whitelisted namespaced class partially imported with an aliased use statement: - do not touch the use statement: see tests for the use statements as to why -- do not prefix the call +- prefix the call: as the call is FQ the use statement is ignored SPEC , 'whitelist' => ['Foo\Bar'], @@ -182,8 +176,7 @@ [ - 'title' => 'Aliased use statements ', + 'title' => 'Aliased use statements', // Default values. If not specified will be the one used 'prefix' => 'Humbug', 'whitelist' => [], @@ -51,7 +51,7 @@ ---- <<<'SPEC' Multiple group use statement: +- transform grouped statements into simple statements - prefix each of them SPEC , @@ -31,14 +32,16 @@ use A\{B}; use A\{B\C, D}; -use \A\B\{C\D, E}; +use \A\B\{C\D as ABCD, E}; ---- <<<'SPEC' Multiple group use statement which are already prefixed: -- do nothing +- transform grouped statements into simple statements SPEC , 'payload' => <<<'PHP' @@ -59,9 +62,11 @@ ---- <<<'SPEC' Multiple group use statement with whitelisted classes: +- transform grouped statements into simple statements - prefix each of them: only actual usages will be whitelisted, not the use statements SPEC , @@ -86,9 +92,11 @@ ---- , - * 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' => 'Use statements for functions with group statements', - // Default values. If not specified will be the one used - 'prefix' => 'Humbug', - 'whitelist' => [], - ], - - <<<'PHP' -nodes[] = deep_clone($node); + $this->nodes[] = $originalName; + + $this->mapping[(string) $node->name] = $originalName->name; } public function findNamespaceForNode(Node $node): ?Name @@ -58,12 +66,21 @@ private function getNodeNamespace(Node $node): ?Name $parentNode = AppendParentNode::getParent($node); if ($parentNode instanceof Namespace_) { - return $parentNode->name; + return $this->mapping[(string) $parentNode->name]; } return $this->getNodeNamespace($parentNode); } + public function getCurrentNamespaceName(): ?Name + { + if (0 === count($this->nodes)) { + return null; + } + + return end($this->nodes)->name; + } + /** * @inheritdoc */ @@ -75,7 +92,7 @@ public function count(): int /** * @inheritdoc */ - public function getIterator() + public function getIterator(): iterable { return new ArrayIterator($this->nodes); } diff --git a/src/NodeVisitor/Collection/UseStmtCollection.php b/src/NodeVisitor/Collection/UseStmtCollection.php index 1bb7b2c0..f74e4e51 100644 --- a/src/NodeVisitor/Collection/UseStmtCollection.php +++ b/src/NodeVisitor/Collection/UseStmtCollection.php @@ -17,6 +17,7 @@ use ArrayIterator; use IteratorAggregate; use PhpParser\Node\Name; +use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; use function Humbug\PhpScoper\deep_clone; @@ -24,13 +25,15 @@ final class UseStmtCollection implements IteratorAggregate { /** - * @var Use_[] + * @var Use_[][] */ - private $nodes = []; + private $nodes = [ + null => [], + ]; - public function add(Use_ $node) + public function add(?Name $namespaceName, Use_ $node): void { - $this->nodes[] = deep_clone($node); + $this->nodes[(string) $namespaceName][] = deep_clone($node); } /** @@ -44,15 +47,18 @@ public function add(Use_ $node) * * will return the use statement for `Bar\Foo`. * - * @param Name $node + * @param Name|null $namespaceName + * @param Name $node * * @return null|Name */ - public function findStatementForNode(Name $node): ?Name + public function findStatementForNode(?Name $namespaceName, Name $node): ?Name { $name = $node->getFirst(); - foreach ($this->nodes as $use_) { + $useStatements = $this->nodes[(string) $namespaceName] ?? []; + + foreach ($useStatements as $use_) { foreach ($use_->uses as $useStatement) { if ($useStatement instanceof UseUse) { if ($name === $useStatement->alias) { @@ -76,7 +82,7 @@ public function findStatementForNode(Name $node): ?Name /** * @inheritdoc */ - public function getIterator() + public function getIterator(): iterable { return new ArrayIterator($this->nodes); } diff --git a/src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php b/src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php new file mode 100644 index 00000000..0c5a4014 --- /dev/null +++ b/src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php @@ -0,0 +1,118 @@ +, + * 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\NodeVisitor\Ignore; + +use Humbug\PhpScoper\NodeVisitor\AppendParentNode; +use Humbug\PhpScoper\NodeVisitor\IgnoreNodeUtility; +use PhpParser\Node; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\New_; +use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt\GroupUse; +use PhpParser\Node\Stmt\Use_; +use PhpParser\Node\Stmt\UseUse; +use PhpParser\NodeVisitorAbstract; + +final class UseIgnoreNodeVisitor extends NodeVisitorAbstract +{ + private $whitelist; + private $whitelister; + + /** + * @param string[] $whitelist + * @param callable $whitelister + */ + public function __construct(array $whitelist, callable $whitelister) + { + $this->whitelist = $whitelist; + $this->whitelister = $whitelister; + } + + /** + * @inheritdoc + */ + public function enterNode(Node $node) + { + if (false === ($node instanceof UseUse) || false === AppendParentNode::hasParent($node)) { + return $node; + } + + $parentNode = AppendParentNode::getParent($node); + + + // The parent node should be prefixed no the current node + // $parentNode instanceof GroupUse + if ( + $this->isGlobalNotWhitelisted($node, $parentNode) + // Is not from the Composer namespace + || 'Composer' === $node->name->getFirst() + || $this->isWhitelisted($node, $parentNode) + ) { + IgnoreNodeUtility::ignoreNode($node); + + return $node; + } + + return $node; + $x = ';'; + // + // For use statements + // + if ($node instanceof UseUse + && AppendParentNode::hasParent($node) + && false === (AppendParentNode::getParent($node) instanceof GroupUse) + && ( + // Is a whitelisted use statement of the global namespace + (1 === count($node->name->parts) && false === ($this->whitelister)($node->name->getFirst())) + // Is not from the Composer namespace + || 'Composer' === $node->name->getFirst() + // Is not a whitelisted class + || ($node->getAttribute('parent') instanceof Use_ + && Use_::TYPE_NORMAL === $node->getAttribute('parent')->type + && in_array((string) $node->name, $this->whitelist) + ) + ) + ) { + $node->setAttribute('phpscoper_ignore', true); + + return $node; + } elseif (false === ($node instanceof FullyQualified) || false === $node->isFullyQualified()) { + return $node; + } + + return $node; + } + + private function isGlobalNotWhitelisted(UseUse $node, Use_ $parentNode): bool + { + return ( + $parentNode->type === Use_::TYPE_NORMAL + && 1 === count($node->name->parts) // Is from the global namespace + && false === ($this->whitelister)($node->name->getFirst()) // Is not (global) whitelisted + ); + } + + private function isWhitelisted(UseUse $node, Use_ $parentNode): bool + { + return ( + $parentNode->type === Use_::TYPE_NORMAL + && 1 !== count($node->name->parts) // Is not from the global namespace + && in_array((string) $node->name, $this->whitelist) // Is whitelisted + ); + } +} diff --git a/src/NodeVisitor/IgnoreNodeVisitor.php b/src/NodeVisitor/IgnoreNodeVisitor.php index eb7c6f49..efdf6c27 100644 --- a/src/NodeVisitor/IgnoreNodeVisitor.php +++ b/src/NodeVisitor/IgnoreNodeVisitor.php @@ -50,8 +50,8 @@ public function enterNode(Node $node) // For use statements // if ($node instanceof UseUse - && $node->hasAttribute('parent') - && false === ($node->getAttribute('parent') instanceof GroupUse) + && AppendParentNode::hasParent($node) + && false === (AppendParentNode::getParent($node) instanceof GroupUse) && ( // Is not a whitelisted use statement of the global namespace (1 === count($node->name->parts) && false === ($this->whitelister)($node->name->getFirst())) @@ -64,8 +64,6 @@ public function enterNode(Node $node) ) ) ) { - $node->setAttribute('phpscoper_ignore', true); - return $node; } elseif (false === ($node instanceof FullyQualified) || false === $node->isFullyQualified()) { return $node; diff --git a/src/NodeVisitor/NameResolver.php b/src/NodeVisitor/NameStmtPrefixer.php similarity index 64% rename from src/NodeVisitor/NameResolver.php rename to src/NodeVisitor/NameStmtPrefixer.php index 4ab69e19..135fcb9d 100644 --- a/src/NodeVisitor/NameResolver.php +++ b/src/NodeVisitor/NameStmtPrefixer.php @@ -18,15 +18,17 @@ use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; 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\Interface_; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\TraitUse; use PhpParser\NodeVisitorAbstract; -final class NameResolver extends NodeVisitorAbstract +final class NameStmtPrefixer extends NodeVisitorAbstract { private $prefix; private $whitelist; @@ -56,7 +58,7 @@ public function __construct( */ public function enterNode(Node $node): Node { - if (false === $node instanceof Name + if (false === ($node instanceof Name) || IgnoreNodeUtility::isNodeIgnored($node) || false === AppendParentNode::hasParent($node) ) { @@ -71,7 +73,6 @@ public function enterNode(Node $node): Node || $parentNode instanceof TraitUse || $parentNode instanceof Interface_ || $parentNode instanceof Node\Stmt\UseUse - || $parentNode instanceof ConstFetch || $parentNode instanceof Node\Stmt\TraitUseAdaptation\Precedence || $parentNode instanceof Node\Stmt\TraitUseAdaptation\Alias ) { @@ -83,24 +84,44 @@ public function enterNode(Node $node): Node // return $node; // } - $resolvedNode = $this->nameResolver->resolveName($node); + $resolvedValue = $this->nameResolver->resolveName($node); + + $resolvedName = $resolvedValue->getName(); // Skip if is already prefixed - if ($this->prefix === $resolvedNode->getFirst()) { - return $resolvedNode; + if ($this->prefix === $resolvedName->getFirst()) { + return $resolvedName; } // Check if the class can be prefixed - if (1 === count($resolvedNode->parts) - && false === ($this->globalWhitelister)($resolvedNode->toString()) + if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)) { + if (1 === count($resolvedName->parts) + && false === ($this->globalWhitelister)($resolvedName->toString()) + ) { + return $resolvedName; + } elseif (1 < count($resolvedName->parts) + && in_array($resolvedName->toString(), $this->whitelist) + ) { + return $resolvedName; + } + } + + // Can we get rid of all the ignore? Seems unnecessary + + if ($parentNode instanceof ConstFetch + && 1 === count($resolvedName->parts) + && null === $resolvedValue->getUse() ) { - return $resolvedNode; - } elseif (1 < count($resolvedNode->parts) - && in_array($resolvedNode->toString(), $this->whitelist) + return $resolvedName; + } + + if ($parentNode instanceof FuncCall + && 1 === count($resolvedName->parts) + && null === $resolvedValue->getUse() ) { - return $resolvedNode; + return $resolvedName; } - return FullyQualified::concat($this->prefix, $resolvedNode->toString(), $resolvedNode->getAttributes()); + return FullyQualified::concat($this->prefix, $resolvedName->toString(), $resolvedName->getAttributes()); } } diff --git a/src/NodeVisitor/ScopeNamespaceStmtNodeVisitor.php b/src/NodeVisitor/NamespaceStmtPrefixer.php similarity index 53% rename from src/NodeVisitor/ScopeNamespaceStmtNodeVisitor.php rename to src/NodeVisitor/NamespaceStmtPrefixer.php index 2679ed34..6fa9f84e 100644 --- a/src/NodeVisitor/ScopeNamespaceStmtNodeVisitor.php +++ b/src/NodeVisitor/NamespaceStmtPrefixer.php @@ -14,13 +14,15 @@ namespace Humbug\PhpScoper\NodeVisitor; +use function Humbug\PhpScoper\deep_clone; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeVisitorAbstract; /** - * Scopes the relevant namespaces. + * Prefixes the relevant namespaces. * * ``` * namespace Foo; @@ -32,13 +34,15 @@ * namespace Humbug\Foo; * ``` */ -final class ScopeNamespaceStmtNodeVisitor extends NodeVisitorAbstract +final class NamespaceStmtPrefixer extends NodeVisitorAbstract { private $prefix; + private $namespaceStatements; - public function __construct(string $prefix) + public function __construct(string $prefix, NamespaceStmtCollection $namespaceStatements) { $this->prefix = $prefix; + $this->namespaceStatements = $namespaceStatements; } /** @@ -46,13 +50,21 @@ public function __construct(string $prefix) */ public function enterNode(Node $node): Node { - if ($node instanceof Namespace_ - && null !== $node->name - && $this->prefix !== $node->name->getFirst() - ) { + if (false === ($node instanceof Namespace_)) { + return $node; + } + /** @var Namespace_ $node */ + + $originalNode = $node; + + if (null !== $node->name && $this->prefix !== $node->name->getFirst()) { + $originalNode = deep_clone($node); + $node->name = Name::concat($this->prefix, $node->name); } + $this->namespaceStatements->add($node, $originalNode); + return $node; } } diff --git a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php index f98e7339..32997b58 100644 --- a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php +++ b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php @@ -3,8 +3,11 @@ namespace Humbug\PhpScoper\NodeVisitor\Resolver; +use Humbug\PhpScoper\NodeVisitor\AppendParentNode; use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; @@ -24,26 +27,45 @@ public function __construct(NamespaceStmtCollection $namespaceStatements, UseStm * * @param Name $node * - * @return Name|FullyQualified + * @return ResolvedValue */ - public function resolveName(Name $node): Name + public function resolveName(Name $node): ResolvedValue { if ($node instanceof FullyQualified) { - return $node; + return new ResolvedValue($node, null, null); } - $useStatement = $this->useStatements->findStatementForNode($node); + $namespaceName = $this->namespaceStatements->findNamespaceForNode($node); - if (null !== $useStatement) { - return FullyQualified::concat($useStatement, $node->slice(1), $node->getAttributes()); + $useName = $this->useStatements->findStatementForNode($namespaceName, $node); + + return new ResolvedValue( + $this->resolveNodeName($node, $namespaceName, $useName), + $namespaceName, + $useName + ); + } + + private function resolveNodeName(Name $name, ?Name $namespace, ?Name $use): Name + { + if (null !== $use) { + return FullyQualified::concat($use, $name->slice(1), $name->getAttributes()); + } + + if (null === $namespace) { + return new FullyQualified($name, $name->getAttributes()); } - $namespaceStatement = $this->namespaceStatements->findNamespaceForNode($node); + $parentNode = AppendParentNode::getParent($name); - if (null !== $namespaceStatement) { - return FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); + if ( + ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall) + && count($name->parts) === 1 + ) { + // Ambiguous name, cannot determine the FQ name + return $name; } - return new FullyQualified($node, $node->getAttributes()); + return FullyQualified::concat($namespace, $name, $name->getAttributes()); } } \ No newline at end of file diff --git a/src/NodeVisitor/Resolver/ResolvedValue.php b/src/NodeVisitor/Resolver/ResolvedValue.php new file mode 100644 index 00000000..486aefc2 --- /dev/null +++ b/src/NodeVisitor/Resolver/ResolvedValue.php @@ -0,0 +1,35 @@ +name = $name; + $this->namespace = $namespace; + $this->use = $use; + } + + public function getName(): Name + { + return $this->name; + } + + public function getNamespace(): ?Name + { + return $this->namespace; + } + + public function getUse(): ?Name + { + return $this->use; + } +} \ No newline at end of file diff --git a/src/NodeVisitor/StringScalarPrefixer.php b/src/NodeVisitor/StringScalarPrefixer.php new file mode 100644 index 00000000..477cdbe8 --- /dev/null +++ b/src/NodeVisitor/StringScalarPrefixer.php @@ -0,0 +1,99 @@ +, + * 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\NodeVisitor; + +use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; +use PhpParser\Node; +use PhpParser\Node\Arg; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\Node\Stmt\TraitUse; +use PhpParser\NodeVisitorAbstract; + +final class StringScalarPrefixer extends NodeVisitorAbstract +{ + private $prefix; + private $whitelist; + private $globalWhitelister; + private $nameResolver; + + /** + * @param string $prefix + * @param string[] $whitelist + * @param callable $globalWhitelister + * @param FullyQualifiedNameResolver $nameResolver + */ + public function __construct( + string $prefix, + array $whitelist, + callable $globalWhitelister, + FullyQualifiedNameResolver $nameResolver + ) { + $this->prefix = $prefix; + $this->whitelist = $whitelist; + $this->globalWhitelister = $globalWhitelister; + $this->nameResolver = $nameResolver; + } + + /** + * @inheritdoc + */ + public function enterNode(Node $node): Node + { + if (false === ($node instanceof String_) + || IgnoreNodeUtility::isNodeIgnored($node) + || false === AppendParentNode::hasParent($node) + ) { + return $node; + } + /** @var String_ $node */ + + $parentNode = AppendParentNode::getParent($node); + + if (false === ($parentNode instanceof Arg)) { + return $node; + } + + $stringName = new Name( + preg_replace('/^\\\\(.+)$/', '$1', $node->value), + $node->getAttributes() + ); + + // Skip if is already prefixed + if ($this->prefix === $stringName->getFirst()) { + $newStringName = $stringName; + // Check if the class can be prefixed: class from the global namespace + } elseif (1 === count($stringName->parts) + && false === ($this->globalWhitelister)($stringName->toString()) + ) { + $newStringName = $stringName; + // Check if the class can be prefixed: regular class + } elseif (1 < count($stringName->parts) + && in_array($stringName->toString(), $this->whitelist) + ) { + $newStringName = $stringName; + } else { + $newStringName = FullyQualified::concat($this->prefix, $stringName->toString(), $stringName->getAttributes()); + } + + return new String_($newStringName->toString(), $node->getAttributes()); + } +} diff --git a/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php b/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php new file mode 100644 index 00000000..a7460038 --- /dev/null +++ b/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php @@ -0,0 +1,88 @@ +, + * 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\NodeVisitor\UseStmt; + +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt\GroupUse; +use PhpParser\Node\Stmt\Use_; +use PhpParser\Node\Stmt\UseUse; +use PhpParser\NodeVisitorAbstract; + +/** + * Scopes relevant group statements. + * + * ``` + * use Foo{X,Y}; + * ``` + * + * => + * + * ``` + * use Humbug\Foo{X,Y}; + * ``` + */ +final class GroupUseStmtTransformer extends NodeVisitorAbstract +{ + private $prefix; + + public function __construct(string $prefix) + { + $this->prefix = $prefix; + } + + public function beforeTraverse(array $nodes) + { + $newNodes = []; + + foreach ($nodes as $node) { + if ($node instanceof GroupUse) { + $uses_ = $this->createUses_($node); + + array_splice($newNodes, count($newNodes), 0, $uses_); + } else { + $newNodes[] = $node; + } + } + + return $newNodes; + } + + /** + * @param GroupUse $node + * + * @return Use_[] + */ + public function createUses_(GroupUse $node): array + { + return array_map( + function (UseUse $use) use ($node): Use_ { + return new Use_( + [ + new UseUse( + Name::concat($node->prefix, $use->name, $use->name->getAttributes()), + $use->alias, + $use->type, + $use->getAttributes() + ) + ], + $node->type, + $node->getAttributes() + ); + }, + $node->uses + ); + } +} diff --git a/src/NodeVisitor/UseStmt/ScopeGroupUseStmtNodeVisitor.php b/src/NodeVisitor/UseStmt/ScopeGroupUseStmtNodeVisitor.php deleted file mode 100644 index a96d2887..00000000 --- a/src/NodeVisitor/UseStmt/ScopeGroupUseStmtNodeVisitor.php +++ /dev/null @@ -1,57 +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\NodeVisitor\UseStmt; - -use PhpParser\Node; -use PhpParser\Node\Name; -use PhpParser\Node\Stmt\GroupUse; -use PhpParser\NodeVisitorAbstract; - -/** - * Scopes relevant group statements. - * - * ``` - * use Foo{X,Y}; - * ``` - * - * => - * - * ``` - * use Humbug\Foo{X,Y}; - * ``` - */ -final class ScopeGroupUseStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - - public function __construct(string $prefix) - { - $this->prefix = $prefix; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if ($node instanceof GroupUse - && $this->prefix !== $node->prefix->getFirst() - ) { - $node->prefix = Name::concat($this->prefix, $node->prefix); - } - - return $node; - } -} diff --git a/src/NodeVisitor/UseStmt/UseStmtCollector.php b/src/NodeVisitor/UseStmt/UseStmtCollector.php index b15abe5e..f771d62b 100644 --- a/src/NodeVisitor/UseStmt/UseStmtCollector.php +++ b/src/NodeVisitor/UseStmt/UseStmtCollector.php @@ -14,6 +14,7 @@ namespace Humbug\PhpScoper\NodeVisitor\UseStmt; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use PhpParser\Node; use PhpParser\Node\Stmt\Use_; @@ -21,10 +22,12 @@ final class UseStmtCollector extends NodeVisitorAbstract { + private $namespaceStatements; private $useStatements; - public function __construct(UseStmtCollection $useStatements) + public function __construct(NamespaceStmtCollection $namespaceStatements, UseStmtCollection $useStatements) { + $this->namespaceStatements = $namespaceStatements; $this->useStatements = $useStatements; } @@ -34,7 +37,9 @@ public function __construct(UseStmtCollection $useStatements) public function enterNode(Node $node): Node { if ($node instanceof Use_) { - $this->useStatements->add($node); + $namespaceName = $this->namespaceStatements->getCurrentNamespaceName(); + + $this->useStatements->add($namespaceName, $node); } return $node; diff --git a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php index 44cf00f4..721f277a 100644 --- a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php +++ b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php @@ -71,7 +71,6 @@ public function enterNode(Node $node) } if (AppendParentNode::hasParent($node) - && false === (AppendParentNode::getParent($node) instanceof GroupUse) // The prefix is not already applied && $this->prefix !== $node->name->getFirst() // Is not an ignored use statement diff --git a/src/Scoper/TraverserFactory/NativeTraverserFactory.php b/src/Scoper/TraverserFactory/NativeTraverserFactory.php index e5bc0990..e4ee2164 100644 --- a/src/Scoper/TraverserFactory/NativeTraverserFactory.php +++ b/src/Scoper/TraverserFactory/NativeTraverserFactory.php @@ -52,18 +52,21 @@ public function create(string $prefix, array $whitelist, callable $globalWhiteli $nameResolver = new FullyQualifiedNameResolver($namespaceStatements, $useStatements); + $this->traverser->addVisitor(new NodeVisitor\UseStmt\GroupUseStmtTransformer($prefix, $whitelist, $whitelistedStatements)); + $this->traverser->addVisitor(new NodeVisitor\AppendParentNode()); + $this->traverser->addVisitor(new NodeVisitor\Ignore\UseIgnoreNodeVisitor($whitelist, $globalWhitelister)); $this->traverser->addVisitor(new NodeVisitor\IgnoreNodeVisitor($whitelist, $globalWhitelister)); - $this->traverser->addVisitor(new NodeVisitor\NamespaceStmtCollector($namespaceStatements)); - $this->traverser->addVisitor(new NodeVisitor\ScopeNamespaceStmtNodeVisitor($prefix)); + $this->traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $namespaceStatements)); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($useStatements)); + $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements)); $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $whitelistedStatements)); // $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeSingleLevelUseAliasVisitor($prefix)); // $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeGroupUseStmtNodeVisitor($prefix)); - $this->traverser->addVisitor(new NodeVisitor\NameResolver($prefix, $whitelist, $globalWhitelister, $nameResolver)); + $this->traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); + $this->traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); // $this->traverser->addVisitor(new NodeVisitor\ScopeFullyQualifiedNodeVisitor($prefix)); // $this->traverser->addVisitor(new NodeVisitor\ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor($prefix, $globalWhitelister)); diff --git a/tests/Scoper/PhpScoperTest.php b/tests/Scoper/PhpScoperTest.php index 49849ced..a2d5c03d 100644 --- a/tests/Scoper/PhpScoperTest.php +++ b/tests/Scoper/PhpScoperTest.php @@ -303,14 +303,15 @@ public function test_can_scope_valid_files(string $spec, string $content, string $content $titleSeparator -ACTUAL +EXPECTED $titleSeparator -$actual +$expected $titleSeparator -EXPECTED +ACTUAL $titleSeparator -$expected +$actual + ------------------------------------------------------------------------------- OUTPUT ); From 54e13bcbda8e3afaab6e34d9d3417bf6ca6b3e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 8 Sep 2017 00:01:11 +0100 Subject: [PATCH 4/9] Fix tests --- .../NodeVisitor/Resolver/NameResolverTest.php | 194 ------------------ 1 file changed, 194 deletions(-) delete mode 100644 tests/NodeVisitor/Resolver/NameResolverTest.php diff --git a/tests/NodeVisitor/Resolver/NameResolverTest.php b/tests/NodeVisitor/Resolver/NameResolverTest.php deleted file mode 100644 index 30fe3bfa..00000000 --- a/tests/NodeVisitor/Resolver/NameResolverTest.php +++ /dev/null @@ -1,194 +0,0 @@ -addVisitor(new AppendParentNode()); - $traverser->addVisitor(new NamespaceStmtCollector($namespaceStatements)); - $traverser->addVisitor(new UseStmtCollector($useStatements)); - - return $traverser; - } - }; - - self::$createScoper = function () use ($nodeTraverserFactory): PhpScoper { - return new PhpScoper( - create_parser(), - new FakeScoper(), - $nodeTraverserFactory - ); - }; - } - - /** - * @inheritdoc - */ - public function setUp() - { - $this->scoper = self::$createScoper(); - - if (null === $this->tmp) { - $this->cwd = getcwd(); - $this->tmp = make_tmp_dir('scoper', __CLASS__); - } - - chdir($this->tmp); - } - /** - * @inheritdoc - */ - public function tearDown() - { - chdir($this->cwd); - remove_dir($this->tmp); - } - - /** - * @dataProvider provideFiles - */ - public function test_can_scope_valid_files(string $content, string $prefix, string $expected) - { - $filePath = escape_path($this->tmp.'/file.php'); - touch($filePath); - file_put_contents($filePath, $content); - - $patchers = [create_fake_patcher()]; - - $whitelist = []; - - $whitelister = function (string $className) { - return 'AppKernel' === $className; - }; - - $actual = $this->scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister); - - $this->assertSame($expected, $actual); - } - - public function provideFiles() - { - // - // Namespace declaration - // - // ============================ - yield '[Namespace declaration] no declaration' => [ - <<<'PHP' - [ - <<<'PHP' - [ - <<<'PHP' - [ - <<<'PHP' - [ - <<<'PHP' - Date: Fri, 8 Sep 2017 09:44:49 +0100 Subject: [PATCH 5/9] Cleanup --- coverage.clover | 388 ++++++++++++++++++ .../Collection/NamespaceStmtCollection.php | 38 +- .../Collection/UseStmtCollection.php | 4 + ...peFunctionCallArgumentsStmtNodeVisitor.php | 77 ---- .../ScopeFunctionCallStmtNodeVisitor.php | 92 ----- .../ScopeStaticCallStmtNodeVisitor.php | 101 ----- .../Ignore/UseIgnoreNodeVisitor.php | 118 ------ src/NodeVisitor/IgnoreNodeUtility.php | 29 -- src/NodeVisitor/IgnoreNodeVisitor.php | 128 ------ src/NodeVisitor/NameStmtPrefixer.php | 62 +-- src/NodeVisitor/NamespaceStmtCollector.php | 42 -- src/NodeVisitor/NamespaceStmtPrefixer.php | 24 +- .../NewStmt/ScopeNewStmtNodeVisitor.php | 86 ---- .../ScopeSingleLevelNewStmtNodeVisitor.php | 86 ---- .../Resolver/FullyQualifiedNameResolver.php | 11 +- src/NodeVisitor/ScopeConstStmtNodeVisitor.php | 95 ----- .../ScopeFullyQualifiedNodeVisitor.php | 50 --- ...ElementsFromGlobalNamespaceNodeVisitor.php | 59 --- src/NodeVisitor/StringScalarPrefixer.php | 41 +- .../UseStmt/GroupUseStmtTransformer.php | 36 +- .../UseStmt/IgnoreUseStmtNodeVisitor.php | 49 --- .../ScopeSingleLevelUseAliasVisitor.php | 99 ----- src/NodeVisitor/UseStmt/UseStmtCollector.php | 4 + src/NodeVisitor/UseStmt/UseStmtPrefixer.php | 104 +++-- src/NodeVisitor/WhitelistedStatements.php | 41 -- src/Scoper/TraverserFactory.php | 4 - .../NativeTraverserFactory.php | 20 +- 27 files changed, 567 insertions(+), 1321 deletions(-) create mode 100644 coverage.clover delete mode 100644 src/NodeVisitor/FunctionStmt/ScopeFunctionCallArgumentsStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php delete mode 100644 src/NodeVisitor/IgnoreNodeUtility.php delete mode 100644 src/NodeVisitor/IgnoreNodeVisitor.php delete mode 100644 src/NodeVisitor/NamespaceStmtCollector.php delete mode 100644 src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/ScopeConstStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/ScopeFullyQualifiedNodeVisitor.php delete mode 100644 src/NodeVisitor/ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor.php delete mode 100644 src/NodeVisitor/UseStmt/IgnoreUseStmtNodeVisitor.php delete mode 100644 src/NodeVisitor/UseStmt/ScopeSingleLevelUseAliasVisitor.php delete mode 100644 src/NodeVisitor/WhitelistedStatements.php diff --git a/coverage.clover b/coverage.clover new file mode 100644 index 00000000..2d6466ee --- /dev/null +++ b/coverage.clover @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NodeVisitor/Collection/NamespaceStmtCollection.php b/src/NodeVisitor/Collection/NamespaceStmtCollection.php index 87c2247b..a6b23d15 100644 --- a/src/NodeVisitor/Collection/NamespaceStmtCollection.php +++ b/src/NodeVisitor/Collection/NamespaceStmtCollection.php @@ -24,6 +24,10 @@ use PhpParser\Node\Stmt\Namespace_; use function Humbug\PhpScoper\deep_clone; +/** + * Utility class collecting all the namespaces for the scoped files allowing to easily find the namespace to which + * belongs a node. + */ final class NamespaceStmtCollection implements IteratorAggregate, Countable { /** @@ -37,6 +41,10 @@ final class NamespaceStmtCollection implements IteratorAggregate, Countable */ private $mapping = []; + /** + * @param Namespace_ $node New namespace, may have been prefixed. + * @param Namespace_ $originalName Original unchanged namespace. + */ public function add(Namespace_ $node, Namespace_ $originalName) { $this->nodes[] = $originalName; @@ -57,21 +65,6 @@ public function findNamespaceForNode(Node $node): ?Name return $this->nodes[0]->name; } - private function getNodeNamespace(Node $node): ?Name - { - if (false === AppendParentNode::hasParent($node)) { - return null; - } - - $parentNode = AppendParentNode::getParent($node); - - if ($parentNode instanceof Namespace_) { - return $this->mapping[(string) $parentNode->name]; - } - - return $this->getNodeNamespace($parentNode); - } - public function getCurrentNamespaceName(): ?Name { if (0 === count($this->nodes)) { @@ -89,6 +82,21 @@ public function count(): int return count($this->nodes); } + private function getNodeNamespace(Node $node): ?Name + { + if (false === AppendParentNode::hasParent($node)) { + return null; + } + + $parentNode = AppendParentNode::getParent($node); + + if ($parentNode instanceof Namespace_) { + return $this->mapping[(string) $parentNode->name]; + } + + return $this->getNodeNamespace($parentNode); + } + /** * @inheritdoc */ diff --git a/src/NodeVisitor/Collection/UseStmtCollection.php b/src/NodeVisitor/Collection/UseStmtCollection.php index f74e4e51..6c779a64 100644 --- a/src/NodeVisitor/Collection/UseStmtCollection.php +++ b/src/NodeVisitor/Collection/UseStmtCollection.php @@ -22,6 +22,10 @@ use PhpParser\Node\Stmt\UseUse; use function Humbug\PhpScoper\deep_clone; +/** + * Utility class collecting all the use statements for the scoped files allowing to easily find the use which a node + * may use. + */ final class UseStmtCollection implements IteratorAggregate { /** diff --git a/src/NodeVisitor/FunctionStmt/ScopeFunctionCallArgumentsStmtNodeVisitor.php b/src/NodeVisitor/FunctionStmt/ScopeFunctionCallArgumentsStmtNodeVisitor.php deleted file mode 100644 index 8db397d3..00000000 --- a/src/NodeVisitor/FunctionStmt/ScopeFunctionCallArgumentsStmtNodeVisitor.php +++ /dev/null @@ -1,77 +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\NodeVisitor\FunctionStmt; - -use PhpParser\Node; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Name; -use PhpParser\Node\Scalar\String_; -use PhpParser\NodeVisitorAbstract; - -final class ScopeFunctionCallArgumentsStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $whitelist; - private $functions; - - /** - * @param string $prefix - * @param string[] $whitelist - * @param string[] $functions Functions which first parameter should be prefixed. - */ - public function __construct(string $prefix, array $whitelist, array $functions) - { - $this->prefix = $prefix; - $this->whitelist = $whitelist; - $this->functions = $functions; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if (!$node instanceof FuncCall || null === $node->name) { - return $node; - } - - if (!$node->name instanceof Name) { - return $node; - } - - if (!in_array($node->name->getFirst(), $this->functions)) { - return $node; - } - - $value = $node->args[0]->value; - if (!$value instanceof String_) { - return $node; - } - - $stringValue = ltrim($value->value, '\\'); - - // Do not prefix whitelisted class methods - if (in_array($stringValue, $this->whitelist)) { - return $node; - } - - // Do not prefix function calls belonging to the global namespace - if (false !== strstr($stringValue, '\\')) { - $value->value = ($value->value !== $stringValue ? '\\' : '').$this->prefix.'\\'.$stringValue; - } - - return $node; - } -} diff --git a/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php b/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php deleted file mode 100644 index f29584ed..00000000 --- a/src/NodeVisitor/FunctionStmt/ScopeFunctionCallStmtNodeVisitor.php +++ /dev/null @@ -1,92 +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\NodeVisitor\FunctionStmt; - -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use PhpParser\Node; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\NodeVisitorAbstract; - -final class ScopeFunctionCallStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $namespaceStatements; - private $useStatements; - private $whitelist; - - public function __construct( - string $prefix, - NamespaceStmtCollection $namespaceStatements, - UseStmtCollection $useStatements, - array $whitelist - ) { - $this->prefix = $prefix; - $this->namespaceStatements = $namespaceStatements; - $this->useStatements = $useStatements; - $this->whitelist = $whitelist; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if (false === ($node instanceof Name) - || false === $node->hasAttribute('parent') - || ($node->hasAttribute('phpscoper_ignore') && $node->getAttribute('phpscoper_ignore')) - ) { - return $node; - } - /** @var Name $node */ - $parentNode = $node->getAttribute('parent'); - - if (false === ($parentNode instanceof FuncCall)) { - return $node; - } - - if (1 === count($node->parts)) { - return $node; - } - - $useStatement = $this->useStatements->findStatementForNode($node->getFirst()); - - $prefix = false; - - if (null === $useStatement) { - if (0 === count($this->namespaceStatements)) { - $newNode = new FullyQualified($node, $node->getAttributes()); - - $prefix = true; - } else { - $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); - - $newNode = FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); - } - } else { - $newNode = FullyQualified::concat($useStatement, $node->slice(1), $node->getAttributes()); - } - - $newNode->setAttribute('phpscoper_ignore', true); - - if ($prefix) { - return FullyQualified::concat($this->prefix, $newNode, $newNode->getAttributes()); - } - - return $node; - } -} diff --git a/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php b/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php deleted file mode 100644 index 1669195e..00000000 --- a/src/NodeVisitor/FunctionStmt/ScopeStaticCallStmtNodeVisitor.php +++ /dev/null @@ -1,101 +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\NodeVisitor\FunctionStmt; - -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use PhpParser\Node; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\NodeVisitorAbstract; - -final class ScopeStaticCallStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $namespaceStatements; - private $useStmtCollection; - private $whitelist; - - public function __construct( - string $prefix, - NamespaceStmtCollection $namespaceStatements, - UseStmtCollection $useStatements, - array $whitelist - ) { - $this->prefix = $prefix; - $this->namespaceStatements = $namespaceStatements; - $this->useStmtCollection = $useStatements; - $this->whitelist = $whitelist; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if (false === ($node instanceof Name) - || false === $node->hasAttribute('parent') - || ($node->hasAttribute('phpscoper_ignore') && $node->getAttribute('phpscoper_ignore')) - ) { - return $node; - } - - $parentNode = $node->getAttribute('parent'); - - if (false === ($parentNode instanceof StaticCall)) { - return $node; - } - - if (1 === count($node->parts)) { - //TODO - $x = ''; - } - - $useStatement = $this->useStmtCollection->findStatementForNode($node->getFirst()); - - $prefix = false; - - if (null === $useStatement) { - if (0 === count($this->namespaceStatements)) { - $newNodeClass = new FullyQualified($node, $node->getAttributes()); - - $prefix = (false === in_array((string) $newNodeClass, $this->whitelist)); - } else { - $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); - - $newNodeClass = FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); - - if (false === in_array((string) $newNodeClass, $this->whitelist)) { - return $node; - } - } - } else { - $newNodeClass = FullyQualified::concat($useStatement, $node->slice(1), $node->getAttributes()); - - if (false === in_array((string) $newNodeClass, $this->whitelist)) { - return $node; - } - } - - $newNodeClass->setAttribute('phpscoper_ignore', true); - - if ($prefix) { - return FullyQualified::concat($this->prefix, $newNodeClass, $newNodeClass->getAttributes()); - } - - return $newNodeClass; - } -} diff --git a/src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php b/src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php deleted file mode 100644 index 0c5a4014..00000000 --- a/src/NodeVisitor/Ignore/UseIgnoreNodeVisitor.php +++ /dev/null @@ -1,118 +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\NodeVisitor\Ignore; - -use Humbug\PhpScoper\NodeVisitor\AppendParentNode; -use Humbug\PhpScoper\NodeVisitor\IgnoreNodeUtility; -use PhpParser\Node; -use PhpParser\Node\Expr\ClassConstFetch; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\New_; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Stmt\GroupUse; -use PhpParser\Node\Stmt\Use_; -use PhpParser\Node\Stmt\UseUse; -use PhpParser\NodeVisitorAbstract; - -final class UseIgnoreNodeVisitor extends NodeVisitorAbstract -{ - private $whitelist; - private $whitelister; - - /** - * @param string[] $whitelist - * @param callable $whitelister - */ - public function __construct(array $whitelist, callable $whitelister) - { - $this->whitelist = $whitelist; - $this->whitelister = $whitelister; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node) - { - if (false === ($node instanceof UseUse) || false === AppendParentNode::hasParent($node)) { - return $node; - } - - $parentNode = AppendParentNode::getParent($node); - - - // The parent node should be prefixed no the current node - // $parentNode instanceof GroupUse - if ( - $this->isGlobalNotWhitelisted($node, $parentNode) - // Is not from the Composer namespace - || 'Composer' === $node->name->getFirst() - || $this->isWhitelisted($node, $parentNode) - ) { - IgnoreNodeUtility::ignoreNode($node); - - return $node; - } - - return $node; - $x = ';'; - // - // For use statements - // - if ($node instanceof UseUse - && AppendParentNode::hasParent($node) - && false === (AppendParentNode::getParent($node) instanceof GroupUse) - && ( - // Is a whitelisted use statement of the global namespace - (1 === count($node->name->parts) && false === ($this->whitelister)($node->name->getFirst())) - // Is not from the Composer namespace - || 'Composer' === $node->name->getFirst() - // Is not a whitelisted class - || ($node->getAttribute('parent') instanceof Use_ - && Use_::TYPE_NORMAL === $node->getAttribute('parent')->type - && in_array((string) $node->name, $this->whitelist) - ) - ) - ) { - $node->setAttribute('phpscoper_ignore', true); - - return $node; - } elseif (false === ($node instanceof FullyQualified) || false === $node->isFullyQualified()) { - return $node; - } - - return $node; - } - - private function isGlobalNotWhitelisted(UseUse $node, Use_ $parentNode): bool - { - return ( - $parentNode->type === Use_::TYPE_NORMAL - && 1 === count($node->name->parts) // Is from the global namespace - && false === ($this->whitelister)($node->name->getFirst()) // Is not (global) whitelisted - ); - } - - private function isWhitelisted(UseUse $node, Use_ $parentNode): bool - { - return ( - $parentNode->type === Use_::TYPE_NORMAL - && 1 !== count($node->name->parts) // Is not from the global namespace - && in_array((string) $node->name, $this->whitelist) // Is whitelisted - ); - } -} diff --git a/src/NodeVisitor/IgnoreNodeUtility.php b/src/NodeVisitor/IgnoreNodeUtility.php deleted file mode 100644 index c6030517..00000000 --- a/src/NodeVisitor/IgnoreNodeUtility.php +++ /dev/null @@ -1,29 +0,0 @@ -hasAttribute(self::IGNORE_NODE_ATTRIBUTE) - && true === $node->getAttribute(self::IGNORE_NODE_ATTRIBUTE) - ); - } - - public static function ignoreNode(Node $node): void - { - $node->setAttribute(self::IGNORE_NODE_ATTRIBUTE, true); - } - - private function __construct() - { - } -} \ No newline at end of file diff --git a/src/NodeVisitor/IgnoreNodeVisitor.php b/src/NodeVisitor/IgnoreNodeVisitor.php deleted file mode 100644 index efdf6c27..00000000 --- a/src/NodeVisitor/IgnoreNodeVisitor.php +++ /dev/null @@ -1,128 +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\NodeVisitor; - -use PhpParser\Node; -use PhpParser\Node\Expr\ClassConstFetch; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\New_; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Stmt\GroupUse; -use PhpParser\Node\Stmt\Use_; -use PhpParser\Node\Stmt\UseUse; -use PhpParser\NodeVisitorAbstract; - -final class IgnoreNodeVisitor extends NodeVisitorAbstract -{ - private $whitelist; - private $whitelister; - - /** - * @param string[] $whitelist - * @param callable $whitelister - */ - public function __construct(array $whitelist, callable $whitelister) - { - $this->whitelist = $whitelist; - $this->whitelister = $whitelister; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node) - { - // - // For use statements - // - if ($node instanceof UseUse - && AppendParentNode::hasParent($node) - && false === (AppendParentNode::getParent($node) instanceof GroupUse) - && ( - // Is not a whitelisted use statement of the global namespace - (1 === count($node->name->parts) && false === ($this->whitelister)($node->name->getFirst())) - // Is not from the Composer namespace - || 'Composer' === $node->name->getFirst() - // Is not a whitelisted class - || ($node->getAttribute('parent') instanceof Use_ - && Use_::TYPE_NORMAL === $node->getAttribute('parent')->type - && in_array((string) $node->name, $this->whitelist) - ) - ) - ) { - return $node; - } elseif (false === ($node instanceof FullyQualified) || false === $node->isFullyQualified()) { - return $node; - } - - /** @var FullyQualified $node */ - - // Is a fully qualified call from the global namespace which is not whitelisted - if (1 === count($node->parts) - && (false === ($this->whitelister)($node->getFirst())) - ) { - $node->setAttribute('phpscoper_ignore', true); - - return $node; - } - - if (false === $node->hasAttribute('parent')) { - return $node; - } - - $parentNode = $node->getAttribute('parent'); - - // Is a static method call of a whitelisted class - if ($parentNode instanceof StaticCall - && in_array((string) $parentNode->class, $this->whitelist) - ) { - $node->setAttribute('phpscoper_ignore', true); - - return $node; - } - -// // Is a method call of a whitelisted class -// if ($parentNode instanceof FuncCall -// && $parentNode->name instanceof Name -// && in_array((string) $parentNode->name->slice(0, -1), $this->whitelist) -// ) { -// $node->setAttribute('phpscoper_ignore', true); -// -// return $node; -// } - - // Is a new instance of a whitelisted class - if ($parentNode instanceof New_ - && in_array((string) $parentNode->class->toString(), $this->whitelist) - ) { - $node->setAttribute('phpscoper_ignore', true); - - return $node; - } - - // Is a constant call of a whitelisted class - if ($parentNode instanceof ClassConstFetch - && in_array((string) $node, $this->whitelist) - ) { - $node->setAttribute('phpscoper_ignore', true); - - return $node; - } - - return $node; - } -} diff --git a/src/NodeVisitor/NameStmtPrefixer.php b/src/NodeVisitor/NameStmtPrefixer.php index 135fcb9d..82172bb9 100644 --- a/src/NodeVisitor/NameStmtPrefixer.php +++ b/src/NodeVisitor/NameStmtPrefixer.php @@ -21,13 +21,21 @@ 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\Interface_; -use PhpParser\Node\Stmt\Namespace_; -use PhpParser\Node\Stmt\TraitUse; use PhpParser\NodeVisitorAbstract; +/** + * ``` + * new Foo\Bar(); + * ``` + * + * => + * + * ``` + * new \Humbug\Foo\Bar(); + * ``` + * + * @private + */ final class NameStmtPrefixer extends NodeVisitorAbstract { private $prefix; @@ -58,33 +66,29 @@ public function __construct( */ public function enterNode(Node $node): Node { - if (false === ($node instanceof Name) - || IgnoreNodeUtility::isNodeIgnored($node) - || false === AppendParentNode::hasParent($node) - ) { - return $node; - } - /** @var Name $node */ - - $parentNode = AppendParentNode::getParent($node); + return ($node instanceof Name && AppendParentNode::hasParent($node)) + ? $this->prefixName($node) + : $node + ; + } - if ($parentNode instanceof Namespace_ - || $parentNode instanceof Class_ - || $parentNode instanceof TraitUse - || $parentNode instanceof Interface_ - || $parentNode instanceof Node\Stmt\UseUse - || $parentNode instanceof Node\Stmt\TraitUseAdaptation\Precedence - || $parentNode instanceof Node\Stmt\TraitUseAdaptation\Alias + private function prefixName(Name $name): Node + { + $parentNode = AppendParentNode::getParent($name); + + if (false === ( + $parentNode instanceof ConstFetch + || $parentNode instanceof ClassConstFetch + || $parentNode instanceof Node\Param + || $parentNode instanceof FuncCall + || $parentNode instanceof Node\Expr\StaticCall + || $parentNode instanceof Node\Expr\New_ + ) ) { - return $node; + return $name; } -// if (false === ($parentNode instanceof ClassConstFetch) -// ) { -// return $node; -// } - - $resolvedValue = $this->nameResolver->resolveName($node); + $resolvedValue = $this->nameResolver->resolveName($name); $resolvedName = $resolvedValue->getName(); @@ -106,8 +110,6 @@ public function enterNode(Node $node): Node } } - // Can we get rid of all the ignore? Seems unnecessary - if ($parentNode instanceof ConstFetch && 1 === count($resolvedName->parts) && null === $resolvedValue->getUse() diff --git a/src/NodeVisitor/NamespaceStmtCollector.php b/src/NodeVisitor/NamespaceStmtCollector.php deleted file mode 100644 index 76d3ac76..00000000 --- a/src/NodeVisitor/NamespaceStmtCollector.php +++ /dev/null @@ -1,42 +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\NodeVisitor; - -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use PhpParser\Node; -use PhpParser\Node\Stmt\Namespace_; -use PhpParser\NodeVisitorAbstract; - -final class NamespaceStmtCollector extends NodeVisitorAbstract -{ - private $namespaceStatements; - - public function __construct(NamespaceStmtCollection $namespaceStatements) - { - $this->namespaceStatements = $namespaceStatements; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if ($node instanceof Namespace_) { - $this->namespaceStatements->add($node); - } - - return $node; - } -} diff --git a/src/NodeVisitor/NamespaceStmtPrefixer.php b/src/NodeVisitor/NamespaceStmtPrefixer.php index 6fa9f84e..04cb8111 100644 --- a/src/NodeVisitor/NamespaceStmtPrefixer.php +++ b/src/NodeVisitor/NamespaceStmtPrefixer.php @@ -50,21 +50,25 @@ public function __construct(string $prefix, NamespaceStmtCollection $namespaceSt */ public function enterNode(Node $node): Node { - if (false === ($node instanceof Namespace_)) { - return $node; - } - /** @var Namespace_ $node */ + return ($node instanceof Namespace_) + ? $this->prefixNamespaceStmt($node) + : $node + ; + } - $originalNode = $node; + private function prefixNamespaceStmt(Namespace_ $namespace): Node + { + $originalNamespace = $namespace; - if (null !== $node->name && $this->prefix !== $node->name->getFirst()) { - $originalNode = deep_clone($node); + if (null !== $namespace->name && $this->prefix !== $namespace->name->getFirst()) { + //TODO: try to get rid of the deep_clone + $originalNamespace = deep_clone($namespace); - $node->name = Name::concat($this->prefix, $node->name); + $namespace->name = Name::concat($this->prefix, $namespace->name); } - $this->namespaceStatements->add($node, $originalNode); + $this->namespaceStatements->add($namespace, $originalNamespace); - return $node; + return $namespace; } } diff --git a/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php b/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php deleted file mode 100644 index 417ee75c..00000000 --- a/src/NodeVisitor/NewStmt/ScopeNewStmtNodeVisitor.php +++ /dev/null @@ -1,86 +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\NodeVisitor\NewStmt; - -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use PhpParser\Node; -use PhpParser\Node\Expr\New_; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\NodeVisitorAbstract; - -final class ScopeNewStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $namespaceStatements; - private $useStatements; - private $whitelist; - - public function __construct( - string $prefix, - NamespaceStmtCollection $namespaceStatements, - UseStmtCollection $useStatements, - array $whitelist - ) { - $this->prefix = $prefix; - $this->namespaceStatements = $namespaceStatements; - $this->useStatements = $useStatements; - $this->whitelist = $whitelist; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if (false === ($node instanceof Name) || false === $node->hasAttribute('parent') || 1 === count($node->parts)) { - return $node; - } - - $parentNode = $node->getAttribute('parent'); - -// if (false === ($parentNode instanceof New_)) { -// return $node; -// } -// -// if (1 === count($node->parts)) { -// //TODO -// $x = ''; -// } - - $useStatement = $this->useStatements->findStatementForNode($node->getFirst()); - - if (null === $useStatement) { - if (0 === count($this->namespaceStatements)) { - return $node; - } - - $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); - - $newNode = FullyQualified::concat($namespaceStatement, $node, $node->getAttributes()); - } else { - $newNode = FullyQualified::concat($useStatement, $node->slice(1), $node->getAttributes()); - } - - $newNode->setAttribute('phpscoper_ignore', true); - - if (in_array((string) $newNode, $this->whitelist)) { - return $newNode; - } - - return $node; - } -} diff --git a/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php b/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php deleted file mode 100644 index 2f7f7f52..00000000 --- a/src/NodeVisitor/NewStmt/ScopeSingleLevelNewStmtNodeVisitor.php +++ /dev/null @@ -1,86 +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\NodeVisitor\NewStmt; - -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use PhpParser\Node; -use PhpParser\Node\Expr\New_; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\NodeVisitorAbstract; - -final class ScopeSingleLevelNewStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $namespaceStatements; - private $useStatements; - private $whitelist; - - public function __construct( - string $prefix, - NamespaceStmtCollection $namespaceStatements, - UseStmtCollection $useStatements, - array $whitelist - ) { - $this->prefix = $prefix; - $this->namespaceStatements = $namespaceStatements; - $this->useStatements = $useStatements; - $this->whitelist = $whitelist; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if (false === ($node instanceof New_)) { - return $node; - } - /** @var New_ $node */ - if (false === ($node->class instanceof Name)) { - return $node; - } - - /** @var Name $nodeClass */ - $nodeClass = $node->class; - - if (1 !== count($nodeClass->parts)) { - return $node; - } - - $useStatement = $this->useStatements->findStatementForNode($nodeClass->getFirst()); - - if (null === $useStatement) { - if (0 === count($this->namespaceStatements)) { - return $node; - } - - $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); - - $newNodeClass = FullyQualified::concat($namespaceStatement, $nodeClass, $nodeClass->getAttributes()); - } else { - $newNodeClass = FullyQualified::concat($useStatement, $nodeClass->slice(1), $nodeClass->getAttributes()); - } - - $newNodeClass->setAttribute('phpscoper_ignore', true); - - if (in_array((string) $newNodeClass, $this->whitelist)) { - return new New_($newNodeClass, $node->args, $node->getAttributes()); - } - - return $node; - } -} diff --git a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php index 32997b58..6f9885e4 100644 --- a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php +++ b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php @@ -11,6 +11,10 @@ use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; +/** + * Attempts to resolve the node name into a fully qualified node. Returns a valid (non fully-qualified) name node on + * failure. + */ final class FullyQualifiedNameResolver { private $namespaceStatements; @@ -22,13 +26,6 @@ public function __construct(NamespaceStmtCollection $namespaceStatements, UseStm $this->useStatements = $useStatements; } - /** - * Attempts to resolve the node name into a fully qualified node. Returns a valid name node on failure. - * - * @param Name $node - * - * @return ResolvedValue - */ public function resolveName(Name $node): ResolvedValue { if ($node instanceof FullyQualified) { diff --git a/src/NodeVisitor/ScopeConstStmtNodeVisitor.php b/src/NodeVisitor/ScopeConstStmtNodeVisitor.php deleted file mode 100644 index 9b1db6f0..00000000 --- a/src/NodeVisitor/ScopeConstStmtNodeVisitor.php +++ /dev/null @@ -1,95 +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\NodeVisitor; - -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr\ClassConstFetch; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\NodeVisitorAbstract; - -final class ScopeConstStmtNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $namespaceStatements; - private $useStmtCollection; - private $whitelist; - - public function __construct( - string $prefix, - NamespaceStmtCollection $namespaceStatements, - UseStmtCollection $useStatements, - array $whitelist - ) { - $this->prefix = $prefix; - $this->namespaceStatements = $namespaceStatements; - $this->useStmtCollection = $useStatements; - $this->whitelist = $whitelist; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if (false === ($node instanceof ClassConstFetch)) { - return $node; - } - /** @var ClassConstFetch $node */ - $constClassNode = $node->class; - - if (false === ($constClassNode instanceof Name)) { - return $node; - } - /* @var Name $useStatement */ - - if ($node->hasAttribute('parent') && $node->getAttribute('parent') instanceof Arg) { - return $node; - } - - $useStatement = $this->useStmtCollection->findStatementForNode($constClassNode->getFirst()); - - $prefix = false; - - if (null === $useStatement) { - if (0 === count($this->namespaceStatements)) { - $newClassNode = new FullyQualified($constClassNode, $constClassNode->getAttributes()); - - $prefix = true; - } else { - $namespaceStatement = $this->namespaceStatements->findNamespaceForNode(); - - $newClassNode = FullyQualified::concat($namespaceStatement, $constClassNode, $constClassNode->getAttributes()); - } - } else { - $newClassNode = FullyQualified::concat($useStatement, $constClassNode->slice(1), $constClassNode->getAttributes()); - } - - $newClassNode->setAttribute('phpscoper_ignore', true); - - if (in_array((string) $newClassNode, $this->whitelist)) { - // Continue - } elseif ($prefix) { - $newClassNode = FullyQualified::concat($this->prefix, $newClassNode, $newClassNode->getAttributes()); - } else { - return $node; - } - - return new ClassConstFetch($newClassNode, $node->name, $node->getAttributes()); - } -} diff --git a/src/NodeVisitor/ScopeFullyQualifiedNodeVisitor.php b/src/NodeVisitor/ScopeFullyQualifiedNodeVisitor.php deleted file mode 100644 index 4d541f5d..00000000 --- a/src/NodeVisitor/ScopeFullyQualifiedNodeVisitor.php +++ /dev/null @@ -1,50 +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\NodeVisitor; - -use PhpParser\Node; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\NodeVisitorAbstract; - -/** - * Scopes the relevant fully qualified nodes. - */ -final class ScopeFullyQualifiedNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - - public function __construct(string $prefix) - { - $this->prefix = $prefix; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if ($node instanceof FullyQualified - && false === ( - $node->hasAttribute('phpscoper_ignore') - && true === $node->getAttribute('phpscoper_ignore') - ) - ) { - return new Name('\\'.Name::concat($this->prefix, (string) $node)); - } - - return $node; - } -} diff --git a/src/NodeVisitor/ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor.php b/src/NodeVisitor/ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor.php deleted file mode 100644 index 9870c432..00000000 --- a/src/NodeVisitor/ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor.php +++ /dev/null @@ -1,59 +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\NodeVisitor; - -use PhpParser\Node; -use PhpParser\Node\Name; -use PhpParser\NodeVisitorAbstract; - -/** - * Scopes whitelisted class from the global namespace. - * - * ``` - * new AppKernel(); - * ``` - * - * => - * - * ``` - * new Humbug\AppKernel(); - * ``` - */ -final class ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor extends NodeVisitorAbstract -{ - private $prefix; - private $whitelister; - - public function __construct(string $prefix, callable $whitelister) - { - $this->prefix = $prefix; - $this->whitelister = $whitelister; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if ($node instanceof Name - && 1 === count($node->parts) - && true === ($this->whitelister)($node->getFirst()) - ) { - return Name::concat($this->prefix, $node->getFirst()); - } - - return $node; - } -} diff --git a/src/NodeVisitor/StringScalarPrefixer.php b/src/NodeVisitor/StringScalarPrefixer.php index 477cdbe8..6f258e64 100644 --- a/src/NodeVisitor/StringScalarPrefixer.php +++ b/src/NodeVisitor/StringScalarPrefixer.php @@ -28,6 +28,19 @@ use PhpParser\Node\Stmt\TraitUse; use PhpParser\NodeVisitorAbstract; +/** + * Prefixes the string scalar values. + * + * ``` + * $x = 'Foo\Bar'; + * ``` + * + * => + * + * ``` + * $x = 'Humbug\Foo\Bar'; + * ``` + */ final class StringScalarPrefixer extends NodeVisitorAbstract { private $prefix; @@ -58,34 +71,34 @@ public function __construct( */ public function enterNode(Node $node): Node { - if (false === ($node instanceof String_) - || IgnoreNodeUtility::isNodeIgnored($node) - || false === AppendParentNode::hasParent($node) - ) { - return $node; - } - /** @var String_ $node */ + return ($node instanceof String_ && AppendParentNode::hasParent($node)) + ? $this->prefixStringScalar($node) + : $node + ; + } - $parentNode = AppendParentNode::getParent($node); + private function prefixStringScalar(String_ $string): Node + { + $parentNode = AppendParentNode::getParent($string); if (false === ($parentNode instanceof Arg)) { - return $node; + return $string; } $stringName = new Name( - preg_replace('/^\\\\(.+)$/', '$1', $node->value), - $node->getAttributes() + preg_replace('/^\\\\(.+)$/', '$1', $string->value), + $string->getAttributes() ); // 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 from the global namespace } elseif (1 === count($stringName->parts) && false === ($this->globalWhitelister)($stringName->toString()) ) { $newStringName = $stringName; - // Check if the class can be prefixed: regular class + // Check if the class can be prefixed: regular class } elseif (1 < count($stringName->parts) && in_array($stringName->toString(), $this->whitelist) ) { @@ -94,6 +107,6 @@ public function enterNode(Node $node): Node $newStringName = FullyQualified::concat($this->prefix, $stringName->toString(), $stringName->getAttributes()); } - return new String_($newStringName->toString(), $node->getAttributes()); + return new String_($newStringName->toString(), $string->getAttributes()); } } diff --git a/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php b/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php index a7460038..1e8eaf54 100644 --- a/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php +++ b/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php @@ -22,27 +22,27 @@ use PhpParser\NodeVisitorAbstract; /** - * Scopes relevant group statements. + * Transforms the grouped use statements into regular use statements which are easier to work with. * * ``` - * use Foo{X,Y}; + * use A\B\{C\D, function b\c, const D}; * ``` * * => * * ``` - * use Humbug\Foo{X,Y}; + * use Humbug\A\B\C\D; + * use function Humbug\A\B\b\c; + * use const Humbug\A\B\D; * ``` + * + * @private */ final class GroupUseStmtTransformer extends NodeVisitorAbstract { - private $prefix; - - public function __construct(string $prefix) - { - $this->prefix = $prefix; - } - + /** + * @inheritdoc + */ public function beforeTraverse(array $nodes) { $newNodes = []; @@ -69,15 +69,15 @@ public function createUses_(GroupUse $node): array { return array_map( function (UseUse $use) use ($node): Use_ { + $newUse = new UseUse( + Name::concat($node->prefix, $use->name, $use->name->getAttributes()), + $use->alias, + $use->type, + $use->getAttributes() + ); + return new Use_( - [ - new UseUse( - Name::concat($node->prefix, $use->name, $use->name->getAttributes()), - $use->alias, - $use->type, - $use->getAttributes() - ) - ], + [$newUse], $node->type, $node->getAttributes() ); diff --git a/src/NodeVisitor/UseStmt/IgnoreUseStmtNodeVisitor.php b/src/NodeVisitor/UseStmt/IgnoreUseStmtNodeVisitor.php deleted file mode 100644 index 8b9a1176..00000000 --- a/src/NodeVisitor/UseStmt/IgnoreUseStmtNodeVisitor.php +++ /dev/null @@ -1,49 +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\NodeVisitor\UseStmt; - -use PhpParser\Node; -use PhpParser\Node\Stmt\GroupUse; -use PhpParser\Node\Stmt\UseUse; -use PhpParser\NodeVisitorAbstract; - -/** - * Whitelists from the scoping the relevant use statements:. - * - * ``` - * use Closure; - * use Foo; - * use Composer\Composer; - * ``` - */ -final class IgnoreUseStmtNodeVisitor extends NodeVisitorAbstract -{ - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - if ($node instanceof UseUse - && $node->hasAttribute('parent') - && false === ($node->getAttribute('parent') instanceof GroupUse) - // If is a single level use statements or part of the Composer namespace - && (1 === count($node->name->parts) || 'Composer' === $node->name->getFirst()) - ) { - $node->setAttribute('phpscoper_ignore', true); - } - - return $node; - } -} diff --git a/src/NodeVisitor/UseStmt/ScopeSingleLevelUseAliasVisitor.php b/src/NodeVisitor/UseStmt/ScopeSingleLevelUseAliasVisitor.php deleted file mode 100644 index 0f2cbbb4..00000000 --- a/src/NodeVisitor/UseStmt/ScopeSingleLevelUseAliasVisitor.php +++ /dev/null @@ -1,99 +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\NodeVisitor\UseStmt; - -use PhpParser\Node; -use PhpParser\Node\Name; -use PhpParser\Node\Stmt\GroupUse; -use PhpParser\Node\Stmt\UseUse; -use PhpParser\NodeVisitorAbstract; - -/** - * Scopes single level use statements with an alias:. - * - * ``` - * use Foo as Bar; - * - * new Foo(); - * ``` - * - * => - * - * ``` - * use Humbug\Foo as Bar; - * - * new Foo(); - * ``` - */ -final class ScopeSingleLevelUseAliasVisitor extends NodeVisitorAbstract -{ - private $prefix; - - /** - * @var array - */ - private $aliases; - - public function __construct(string $prefix) - { - $this->prefix = $prefix; - } - - /** - * @inheritdoc - */ - public function beforeTraverse(array $nodes) - { - $this->aliases = []; - } - - /** - * @inheritdoc - */ - public function enterNode(Node $node): Node - { - // Collate all single level aliases - if ($node instanceof UseUse - && (false === $node->hasAttribute('parent') // double check if this one may be removed - || false === ($node->getAttribute('parent') instanceof GroupUse) - ) - && $this->prefix !== $node->name->getFirst() - // Is a one level use statement - && 1 === count($node->name->parts) - // Has an alias - && $node->alias !== $node->name->getFirst() - ) { - $this->aliases[$node->alias] = $node; - - return $node; - } - - return $this->scopeUseStmtIfUsedInAnyNameAsAliasedNamespace($node); - } - - private function scopeUseStmtIfUsedInAnyNameAsAliasedNamespace(Node $node): Node - { - if ($node instanceof Name - && 1 < count($node->parts) - && in_array($node->getFirst(), array_keys($this->aliases)) - ) { - $nodeToPrefix = $this->aliases[$node->getFirst()]; - $nodeToPrefix->name = Name::concat($this->prefix, $nodeToPrefix->name); - unset($this->aliases[$node->getFirst()]); - } - - return $node; - } -} diff --git a/src/NodeVisitor/UseStmt/UseStmtCollector.php b/src/NodeVisitor/UseStmt/UseStmtCollector.php index f771d62b..738556d7 100644 --- a/src/NodeVisitor/UseStmt/UseStmtCollector.php +++ b/src/NodeVisitor/UseStmt/UseStmtCollector.php @@ -20,6 +20,10 @@ use PhpParser\Node\Stmt\Use_; use PhpParser\NodeVisitorAbstract; +/** + * Collects all the use statements. This allows us to resolve a class/constant/function call into a fully-qualified + * call. + */ final class UseStmtCollector extends NodeVisitorAbstract { private $namespaceStatements; diff --git a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php index 721f277a..9f2d8298 100644 --- a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php +++ b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php @@ -15,35 +15,33 @@ namespace Humbug\PhpScoper\NodeVisitor\UseStmt; use Humbug\PhpScoper\NodeVisitor\AppendParentNode; -use Humbug\PhpScoper\NodeVisitor\IgnoreNodeUtility; -use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; use PhpParser\Node; use PhpParser\Node\Name; -use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; -use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; /** - * Manipulates use statements. + * Prefixes the use statements. + * + * */ final class UseStmtPrefixer extends NodeVisitorAbstract { private $prefix; private $whitelist; - private $whitelistedStatements; + private $globalWhitelister; /** - * @param string $prefix - * @param string[] $whitelist - * @param WhitelistedStatements $whitelistedStatements + * @param string $prefix + * @param string[] $whitelist + * @param callable $globalWhitelister */ - public function __construct(string $prefix, array $whitelist, WhitelistedStatements $whitelistedStatements) + public function __construct(string $prefix, array $whitelist, callable $globalWhitelister) { $this->prefix = $prefix; $this->whitelist = $whitelist; - $this->whitelistedStatements = $whitelistedStatements; + $this->globalWhitelister = $globalWhitelister; } /** @@ -51,56 +49,56 @@ public function __construct(string $prefix, array $whitelist, WhitelistedStateme */ public function enterNode(Node $node) { - if (false === ($node instanceof UseUse)) { - return $node; + if ($node instanceof UseUse && $this->shouldPrefixUseStmt($node)) { + $node->name = Name::concat($this->prefix, $node->name); } - /** @var UseUse $node */ - if (Use_::TYPE_UNKNOWN === $node->type) { - $nodeType = AppendParentNode::getParent($node)->type; - } else { - $nodeType = $node->type; + return $node; + } + + private function shouldPrefixUseStmt(UseUse $use): bool + { + $useType = $this->findUseType($use); + + // If is already from the prefix namespace + if ($this->prefix === $use->name->getFirst()) { + return false; } - // Mark use statements of whitelisted classes - if (Use_::TYPE_NORMAL === $nodeType && in_array((string) $node->name, $this->whitelist) - ) { - $this->whitelistedStatements->addNode($node); + // Is not from the Composer namespace + if ('Composer' === $use->name->getFirst()) { + return false; + } - return $node; + if (1 === count($use->name->parts)) { + return ( + $useType !== Use_::TYPE_NORMAL + || ($this->globalWhitelister)($use->name->getFirst()) + ); } - if (AppendParentNode::hasParent($node) - // The prefix is not already applied - && $this->prefix !== $node->name->getFirst() - // Is not an ignored use statement - && false === IgnoreNodeUtility::isNodeIgnored($node) - ) { - $node->name = Name::concat($this->prefix, $node->name); + return false === ( + $useType === Use_::TYPE_NORMAL + && in_array((string) $use->name, $this->whitelist) + ); + } + + /** + * Finds the type of the use statement. + * + * @param UseUse $use + * + * @return int See \PhpParser\Node\Stmt\Use_ type constants. + */ + private function findUseType(UseUse $use): int + { + if (Use_::TYPE_UNKNOWN === $use->type) { + /** @var Use_ $parentNode */ + $parentNode = AppendParentNode::getParent($use); + + return $parentNode->type; } - return $node; + return $use->type; } -// -// /** -// * Removes use statements of whitelisted classes. -// * -// * {@inheritdoc} -// */ -// public function leaveNode(Node $node) -// { -// if ($node instanceof Use_ && 0 === count($node->uses)) { -// return NodeTraverser::REMOVE_NODE; -// } -// -// if (false === ($node instanceof UseUse)) { -// return $node; -// } -// -// if ($this->whitelistedStatements->has($node)) { -// return NodeTraverser::REMOVE_NODE; -// } -// -// return $node; -// } } diff --git a/src/NodeVisitor/WhitelistedStatements.php b/src/NodeVisitor/WhitelistedStatements.php deleted file mode 100644 index 30a569d2..00000000 --- a/src/NodeVisitor/WhitelistedStatements.php +++ /dev/null @@ -1,41 +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\NodeVisitor; - -use PhpParser\Node; - -final class WhitelistedStatements -{ - /** - * @var Node[] - */ - private $nodes = []; - - public function addNode(Node $node) - { - $this->nodes[] = $node; - } - - public function has(Node $node): bool - { - foreach ($this->nodes as $whitelistedNode) { - if ($node === $whitelistedNode) { - return true; - } - } - - return false; - } -} diff --git a/src/Scoper/TraverserFactory.php b/src/Scoper/TraverserFactory.php index d3d080c7..042bb31e 100644 --- a/src/Scoper/TraverserFactory.php +++ b/src/Scoper/TraverserFactory.php @@ -14,10 +14,6 @@ namespace Humbug\PhpScoper\Scoper; -use Humbug\PhpScoper\NodeVisitor; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; -use PhpParser\NodeTraverser; use PhpParser\NodeTraverserInterface; interface TraverserFactory diff --git a/src/Scoper/TraverserFactory/NativeTraverserFactory.php b/src/Scoper/TraverserFactory/NativeTraverserFactory.php index e4ee2164..fd49ff60 100644 --- a/src/Scoper/TraverserFactory/NativeTraverserFactory.php +++ b/src/Scoper/TraverserFactory/NativeTraverserFactory.php @@ -48,37 +48,21 @@ public function create(string $prefix, array $whitelist, callable $globalWhiteli $namespaceStatements = new NamespaceStmtCollection(); $useStatements = new UseStmtCollection(); - $whitelistedStatements = new WhitelistedStatements(); $nameResolver = new FullyQualifiedNameResolver($namespaceStatements, $useStatements); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\GroupUseStmtTransformer($prefix, $whitelist, $whitelistedStatements)); + $this->traverser->addVisitor(new NodeVisitor\UseStmt\GroupUseStmtTransformer()); $this->traverser->addVisitor(new NodeVisitor\AppendParentNode()); - $this->traverser->addVisitor(new NodeVisitor\Ignore\UseIgnoreNodeVisitor($whitelist, $globalWhitelister)); - $this->traverser->addVisitor(new NodeVisitor\IgnoreNodeVisitor($whitelist, $globalWhitelister)); $this->traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $namespaceStatements)); $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements)); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $whitelistedStatements)); -// $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeSingleLevelUseAliasVisitor($prefix)); -// $this->traverser->addVisitor(new NodeVisitor\UseStmt\ScopeGroupUseStmtNodeVisitor($prefix)); + $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $globalWhitelister)); $this->traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); $this->traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); -// $this->traverser->addVisitor(new NodeVisitor\ScopeFullyQualifiedNodeVisitor($prefix)); -// $this->traverser->addVisitor(new NodeVisitor\ScopeWhitelistedElementsFromGlobalNamespaceNodeVisitor($prefix, $globalWhitelister)); -// $this->traverser->addVisitor(new NodeVisitor\ScopeConstStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - -// $this->traverser->addVisitor(new NodeVisitor\NewStmt\ScopeNewStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); -// $this->traverser->addVisitor(new NodeVisitor\NewStmt\ScopeSingleLevelNewStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - -// $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeFunctionCallArgumentsStmtNodeVisitor($prefix, $whitelist, self::WHITELISTED_FUNCTIONS)); -// $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeStaticCallStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); -// $this->traverser->addVisitor(new NodeVisitor\FunctionStmt\ScopeFunctionCallStmtNodeVisitor($prefix, $namespaceStatements, $useStatements, $whitelist)); - return $this->traverser; } } From 68a5a6f71810db4c5401e5a048cda12c9df7425c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 8 Sep 2017 15:23:32 +0100 Subject: [PATCH 6/9] Remove DeepCopy --- Makefile | 3 +- composer.json | 1 - composer.lock | 88 ++-- coverage.clover | 388 ------------------ .../Collection/NamespaceStmtCollection.php | 2 +- .../Collection/UseStmtCollection.php | 4 +- src/NodeVisitor/NamespaceStmtPrefixer.php | 4 +- src/functions.php | 94 ++++- 8 files changed, 142 insertions(+), 442 deletions(-) delete mode 100644 coverage.clover diff --git a/Makefile b/Makefile index 38c9f201..79fbf466 100644 --- a/Makefile +++ b/Makefile @@ -81,9 +81,10 @@ e2e: bin/scoper.phar fixtures/set005/vendor fixtures/set011/vendor tb: ## Run Blackfire profiling tb: vendor rm -rf build + rm -rf vendor-bin/*/vendor composer install --no-dev --prefer-dist --classmap-authoritative - blackfire --new-reference run bin/php-scoper add-prefix -f -q + blackfire --new-reference run php -d zend.enable_gc=0 bin/php-scoper add-prefix --force --quiet composer install diff --git a/composer.json b/composer.json index dd6be6f6..7086c338 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,6 @@ "require": { "php": "^7.1", - "myclabs/deep-copy": "^1.6", "nikic/php-parser": "^3.0", "ocramius/package-versions": "^1.1", "padraic/phar-updater": "^1.0", diff --git a/composer.lock b/composer.lock index 3677a482..8389934c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,50 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "55fa234c772b01f4312f26b7b98d839d", + "content-hash": "c114eb1cecf32e34a84a446900dbb49e", "packages": [ - { - "name": "myclabs/deep-copy", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2017-04-12T18:52:22+00:00" - }, { "name": "nikic/php-parser", "version": "v3.0.6", @@ -680,6 +638,48 @@ ], "time": "2015-06-14T21:17:01+00:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-04-12T18:52:22+00:00" + }, { "name": "phar-io/manifest", "version": "1.0.1", @@ -2040,7 +2040,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.0" + "php": "^7.1" }, "platform-dev": [] } diff --git a/coverage.clover b/coverage.clover deleted file mode 100644 index 2d6466ee..00000000 --- a/coverage.clover +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NodeVisitor/Collection/NamespaceStmtCollection.php b/src/NodeVisitor/Collection/NamespaceStmtCollection.php index a6b23d15..694a15d7 100644 --- a/src/NodeVisitor/Collection/NamespaceStmtCollection.php +++ b/src/NodeVisitor/Collection/NamespaceStmtCollection.php @@ -22,7 +22,7 @@ use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Namespace_; -use function Humbug\PhpScoper\deep_clone; +use function Humbug\PhpScoper\clone_node; /** * Utility class collecting all the namespaces for the scoped files allowing to easily find the namespace to which diff --git a/src/NodeVisitor/Collection/UseStmtCollection.php b/src/NodeVisitor/Collection/UseStmtCollection.php index 6c779a64..1847460e 100644 --- a/src/NodeVisitor/Collection/UseStmtCollection.php +++ b/src/NodeVisitor/Collection/UseStmtCollection.php @@ -20,7 +20,7 @@ use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; -use function Humbug\PhpScoper\deep_clone; +use function Humbug\PhpScoper\clone_node; /** * Utility class collecting all the use statements for the scoped files allowing to easily find the use which a node @@ -37,7 +37,7 @@ final class UseStmtCollection implements IteratorAggregate public function add(?Name $namespaceName, Use_ $node): void { - $this->nodes[(string) $namespaceName][] = deep_clone($node); + $this->nodes[(string) $namespaceName][] = clone_node($node); } /** diff --git a/src/NodeVisitor/NamespaceStmtPrefixer.php b/src/NodeVisitor/NamespaceStmtPrefixer.php index 04cb8111..2027fec7 100644 --- a/src/NodeVisitor/NamespaceStmtPrefixer.php +++ b/src/NodeVisitor/NamespaceStmtPrefixer.php @@ -14,7 +14,7 @@ namespace Humbug\PhpScoper\NodeVisitor; -use function Humbug\PhpScoper\deep_clone; +use function Humbug\PhpScoper\clone_node; use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use PhpParser\Node; use PhpParser\Node\Name; @@ -62,7 +62,7 @@ private function prefixNamespaceStmt(Namespace_ $namespace): Node if (null !== $namespace->name && $this->prefix !== $namespace->name->getFirst()) { //TODO: try to get rid of the deep_clone - $originalNamespace = deep_clone($namespace); + $originalNamespace = clone_node($namespace); $namespace->name = Name::concat($this->prefix, $namespace->name); } diff --git a/src/functions.php b/src/functions.php index aad3da3d..98abcbdd 100644 --- a/src/functions.php +++ b/src/functions.php @@ -19,6 +19,7 @@ use Humbug\PhpScoper\Console\Command\AddPrefixCommand; use Humbug\PhpScoper\Console\Command\SelfUpdateCommand; use Humbug\PhpScoper\Handler\HandleAddPrefix; +use Humbug\PhpScoper\NodeVisitor\AppendParentNode; use Humbug\PhpScoper\Scoper\Composer\InstalledPackagesScoper; use Humbug\PhpScoper\Scoper\Composer\JsonFileScoper; use Humbug\PhpScoper\Scoper\TraverserFactory\NativeTraverserFactory; @@ -28,10 +29,12 @@ use Humbug\SelfUpdate\Exception\RuntimeException as SelfUpdateRuntimeException; use Humbug\SelfUpdate\Updater; use PackageVersions\Versions; +use PhpParser\Node; use PhpParser\Parser; use PhpParser\ParserFactory; use Symfony\Component\Console\Application as SymfonyApplication; use Symfony\Component\Filesystem\Filesystem; +use UnexpectedValueException; /** * @private @@ -158,11 +161,96 @@ function get_common_path(array $paths): string } /** - * @param $item + * In-house clone functions. Does a partial clone that should be enough to provide the immutability required in some + * places for the scoper. It however does not guarantee a deep cloning as would be horribly slow for no good reasons. + * A better alternative would be to find a way to push immutability upstream in PHP-Parser directly. + * + * @param Node $node + * + * @return Node + */ +function clone_node(Node $node): Node +{ + $clone = deep_clone($node); + + foreach ($node->getAttributes() as $key => $attribute) { + $clone->setAttribute($key, $attribute); + } + + return $clone; +} + +/** + * @param mixed $node * * @return mixed + * + * @internal */ -function deep_clone($item) +function deep_clone($node) { - return (new DeepCopy())->copy($item); + if (is_array($node)) { + return array_map(__FUNCTION__, $node); + } + + if (null === $node || is_scalar($node)) { + return $node; + } + + if ($node instanceof Node\Stmt\Namespace_) { + return new Node\Stmt\Namespace_( + deep_clone($node->name) + ); + } + + if ($node instanceof Node\Name\Relative) { + return new Node\Name\Relative( + deep_clone($node->toString()) + ); + } + + if ($node instanceof Node\Name\FullyQualified) { + return new Node\Name\FullyQualified( + deep_clone($node->toString()) + ); + } + + if ($node instanceof Node\Name) { + return new Node\Name( + deep_clone($node->toString()) + ); + } + + if ($node instanceof Node\Stmt\Use_) { + return new Node\Stmt\Use_( + deep_clone($node->uses), + $node->type + ); + } + + if ($node instanceof Node\Stmt\UseUse) { + return new Node\Stmt\UseUse( + deep_clone($node->name), + $node->alias, + $node->type + ); + } + + if ($node instanceof Node\Stmt\Class_) { + return new Node\Stmt\Class_( + deep_clone($node->name), + [ + 'flags' => deep_clone($node->flags), + 'extends' => deep_clone($node->extends), + 'implements' => deep_clone($node->implements), + ] + ); + } + + throw new UnexpectedValueException( + sprintf( + 'Cannot clone element "%s".', + get_class($node) + ) + ); } From 42a6ebac808ed4e34448cb6b18f45fba4292f754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 8 Sep 2017 23:01:00 +0100 Subject: [PATCH 7/9] Fix CS --- src/NodeVisitor/AppendParentNode.php | 1 - .../Collection/NamespaceStmtCollection.php | 6 ++---- src/NodeVisitor/Collection/UseStmtCollection.php | 3 +-- src/NodeVisitor/NameStmtPrefixer.php | 2 +- src/NodeVisitor/NamespaceStmtPrefixer.php | 2 +- .../Resolver/FullyQualifiedNameResolver.php | 13 ++++++++++++- src/NodeVisitor/Resolver/ResolvedValue.php | 13 ++++++++++++- src/NodeVisitor/StringScalarPrefixer.php | 6 ------ src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php | 1 - src/NodeVisitor/UseStmt/UseStmtPrefixer.php | 6 ++---- src/Scoper/TraverserFactory.php | 4 ++-- .../TraverserFactory/NativeTraverserFactory.php | 1 - src/functions.php | 4 +--- 13 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/NodeVisitor/AppendParentNode.php b/src/NodeVisitor/AppendParentNode.php index 6bc4ab12..000e1aa1 100644 --- a/src/NodeVisitor/AppendParentNode.php +++ b/src/NodeVisitor/AppendParentNode.php @@ -14,7 +14,6 @@ namespace Humbug\PhpScoper\NodeVisitor; -use LogicException; use PhpParser\Node; use PhpParser\NodeVisitorAbstract; diff --git a/src/NodeVisitor/Collection/NamespaceStmtCollection.php b/src/NodeVisitor/Collection/NamespaceStmtCollection.php index 694a15d7..44c2d328 100644 --- a/src/NodeVisitor/Collection/NamespaceStmtCollection.php +++ b/src/NodeVisitor/Collection/NamespaceStmtCollection.php @@ -17,12 +17,10 @@ use ArrayIterator; use Countable; use Humbug\PhpScoper\NodeVisitor\AppendParentNode; -use InvalidArgumentException; use IteratorAggregate; use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Namespace_; -use function Humbug\PhpScoper\clone_node; /** * Utility class collecting all the namespaces for the scoped files allowing to easily find the namespace to which @@ -37,12 +35,12 @@ final class NamespaceStmtCollection implements IteratorAggregate, Countable /** * @var Name|null[] Associative array with the potentially prefixed namespace names as keys and their original name - * as value. + * as value. */ private $mapping = []; /** - * @param Namespace_ $node New namespace, may have been prefixed. + * @param Namespace_ $node New namespace, may have been prefixed. * @param Namespace_ $originalName Original unchanged namespace. */ public function add(Namespace_ $node, Namespace_ $originalName) diff --git a/src/NodeVisitor/Collection/UseStmtCollection.php b/src/NodeVisitor/Collection/UseStmtCollection.php index 1847460e..5f745b84 100644 --- a/src/NodeVisitor/Collection/UseStmtCollection.php +++ b/src/NodeVisitor/Collection/UseStmtCollection.php @@ -17,7 +17,6 @@ use ArrayIterator; use IteratorAggregate; use PhpParser\Node\Name; -use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; use function Humbug\PhpScoper\clone_node; @@ -52,7 +51,7 @@ public function add(?Name $namespaceName, Use_ $node): void * will return the use statement for `Bar\Foo`. * * @param Name|null $namespaceName - * @param Name $node + * @param Name $node * * @return null|Name */ diff --git a/src/NodeVisitor/NameStmtPrefixer.php b/src/NodeVisitor/NameStmtPrefixer.php index 82172bb9..43ca2c23 100644 --- a/src/NodeVisitor/NameStmtPrefixer.php +++ b/src/NodeVisitor/NameStmtPrefixer.php @@ -26,7 +26,7 @@ /** * ``` * new Foo\Bar(); - * ``` + * ```. * * => * diff --git a/src/NodeVisitor/NamespaceStmtPrefixer.php b/src/NodeVisitor/NamespaceStmtPrefixer.php index 2027fec7..c239ca95 100644 --- a/src/NodeVisitor/NamespaceStmtPrefixer.php +++ b/src/NodeVisitor/NamespaceStmtPrefixer.php @@ -14,12 +14,12 @@ namespace Humbug\PhpScoper\NodeVisitor; -use function Humbug\PhpScoper\clone_node; use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeVisitorAbstract; +use function Humbug\PhpScoper\clone_node; /** * Prefixes the relevant namespaces. diff --git a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php index 6f9885e4..8893d87c 100644 --- a/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php +++ b/src/NodeVisitor/Resolver/FullyQualifiedNameResolver.php @@ -1,6 +1,17 @@ , + * 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\NodeVisitor\Resolver; use Humbug\PhpScoper\NodeVisitor\AppendParentNode; @@ -65,4 +76,4 @@ private function resolveNodeName(Name $name, ?Name $namespace, ?Name $use): Name return FullyQualified::concat($namespace, $name, $name->getAttributes()); } -} \ No newline at end of file +} diff --git a/src/NodeVisitor/Resolver/ResolvedValue.php b/src/NodeVisitor/Resolver/ResolvedValue.php index 486aefc2..a0f91982 100644 --- a/src/NodeVisitor/Resolver/ResolvedValue.php +++ b/src/NodeVisitor/Resolver/ResolvedValue.php @@ -1,6 +1,17 @@ , + * 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\NodeVisitor\Resolver; use PhpParser\Node\Name; @@ -32,4 +43,4 @@ public function getUse(): ?Name { return $this->use; } -} \ No newline at end of file +} diff --git a/src/NodeVisitor/StringScalarPrefixer.php b/src/NodeVisitor/StringScalarPrefixer.php index 6f258e64..a925d752 100644 --- a/src/NodeVisitor/StringScalarPrefixer.php +++ b/src/NodeVisitor/StringScalarPrefixer.php @@ -17,15 +17,9 @@ use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; use PhpParser\Node; use PhpParser\Node\Arg; -use PhpParser\Node\Expr\ClassConstFetch; -use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\String_; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\Interface_; -use PhpParser\Node\Stmt\Namespace_; -use PhpParser\Node\Stmt\TraitUse; use PhpParser\NodeVisitorAbstract; /** diff --git a/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php b/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php index 1e8eaf54..d779185d 100644 --- a/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php +++ b/src/NodeVisitor/UseStmt/GroupUseStmtTransformer.php @@ -14,7 +14,6 @@ namespace Humbug\PhpScoper\NodeVisitor\UseStmt; -use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Use_; diff --git a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php index 9f2d8298..55072e3d 100644 --- a/src/NodeVisitor/UseStmt/UseStmtPrefixer.php +++ b/src/NodeVisitor/UseStmt/UseStmtPrefixer.php @@ -23,8 +23,6 @@ /** * Prefixes the use statements. - * - * */ final class UseStmtPrefixer extends NodeVisitorAbstract { @@ -71,10 +69,10 @@ private function shouldPrefixUseStmt(UseUse $use): bool } if (1 === count($use->name->parts)) { - return ( + return $useType !== Use_::TYPE_NORMAL || ($this->globalWhitelister)($use->name->getFirst()) - ); + ; } return false === ( diff --git a/src/Scoper/TraverserFactory.php b/src/Scoper/TraverserFactory.php index 042bb31e..ad6938c0 100644 --- a/src/Scoper/TraverserFactory.php +++ b/src/Scoper/TraverserFactory.php @@ -19,8 +19,8 @@ interface TraverserFactory { /** - * @param string $prefix Prefix to apply to the files. - * @param string[] $whitelist List of classes to exclude from the scoping. + * @param string $prefix Prefix to apply to the files. + * @param string[] $whitelist List of classes to exclude from the scoping. * @param callable $globalWhitelister 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/Scoper/TraverserFactory/NativeTraverserFactory.php b/src/Scoper/TraverserFactory/NativeTraverserFactory.php index fd49ff60..d61923a6 100644 --- a/src/Scoper/TraverserFactory/NativeTraverserFactory.php +++ b/src/Scoper/TraverserFactory/NativeTraverserFactory.php @@ -18,7 +18,6 @@ use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; -use Humbug\PhpScoper\NodeVisitor\WhitelistedStatements; use Humbug\PhpScoper\Scoper\TraverserFactory; use PhpParser\NodeTraverser; use PhpParser\NodeTraverserInterface; diff --git a/src/functions.php b/src/functions.php index 98abcbdd..31a68594 100644 --- a/src/functions.php +++ b/src/functions.php @@ -14,18 +14,16 @@ namespace Humbug\PhpScoper; -use DeepCopy\DeepCopy; use Humbug\PhpScoper\Console\Application; use Humbug\PhpScoper\Console\Command\AddPrefixCommand; use Humbug\PhpScoper\Console\Command\SelfUpdateCommand; use Humbug\PhpScoper\Handler\HandleAddPrefix; -use Humbug\PhpScoper\NodeVisitor\AppendParentNode; use Humbug\PhpScoper\Scoper\Composer\InstalledPackagesScoper; use Humbug\PhpScoper\Scoper\Composer\JsonFileScoper; -use Humbug\PhpScoper\Scoper\TraverserFactory\NativeTraverserFactory; use Humbug\PhpScoper\Scoper\NullScoper; use Humbug\PhpScoper\Scoper\PatchScoper; use Humbug\PhpScoper\Scoper\PhpScoper; +use Humbug\PhpScoper\Scoper\TraverserFactory\NativeTraverserFactory; use Humbug\SelfUpdate\Exception\RuntimeException as SelfUpdateRuntimeException; use Humbug\SelfUpdate\Updater; use PackageVersions\Versions; From a3a1198a43ec2f1de1bcc79f46e218d58ecc1929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 8 Sep 2017 23:09:40 +0100 Subject: [PATCH 8/9] Fix comments --- ...spaced-with-single-level-use-and-alias.php | 90 +++++++++---------- src/NodeVisitor/NamespaceStmtPrefixer.php | 1 - 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/specs/const/global-scope-two-parts-namespaced-with-single-level-use-and-alias.php b/specs/const/global-scope-two-parts-namespaced-with-single-level-use-and-alias.php index dbe28f96..2b114a4c 100644 --- a/specs/const/global-scope-two-parts-namespaced-with-single-level-use-and-alias.php +++ b/specs/const/global-scope-two-parts-namespaced-with-single-level-use-and-alias.php @@ -20,28 +20,28 @@ 'whitelist' => [], ], -// [ -// 'spec' => <<<'SPEC' -//Namespaced constant call with namespace partially imported -//- do not prefix the use statement (cf. tests related to global classes) -//- prefix the call -//- transform the call in a FQ call -//SPEC -// , -// 'payload' => <<<'PHP' -// <<<'SPEC' +Namespaced constant call with namespace partially imported +- do not prefix the use statement (cf. tests related to global classes) +- prefix the call +- transform the call in a FQ call +SPEC + , + 'payload' => <<<'PHP' + <<<'SPEC' @@ -65,27 +65,27 @@ PHP ], -// [ -// 'spec' => <<<'SPEC' -//Namespaced constant call with namespace partially imported -//- do not prefix the use statement (cf. tests related to global classes) -//- prefix the call -//- transform the call in a FQ call -//SPEC -// , -// 'whitelist' => ['Foo\Bar\DUMMY_CONST'], -// 'payload' => <<<'PHP' -// <<<'SPEC' +Namespaced constant call with namespace partially imported +- do not prefix the use statement (cf. tests related to global classes) +- prefix the call +- transform the call in a FQ call +SPEC + , + 'whitelist' => ['Foo\Bar\DUMMY_CONST'], + 'payload' => <<<'PHP' +name && $this->prefix !== $namespace->name->getFirst()) { - //TODO: try to get rid of the deep_clone $originalNamespace = clone_node($namespace); $namespace->name = Name::concat($this->prefix, $namespace->name); From f8f64e6ba2fa8c77c2b3285cce084dd9cb99c24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 8 Sep 2017 23:13:36 +0100 Subject: [PATCH 9/9] Revert traverser factory interface --- src/Scoper/PhpScoper.php | 4 +- src/Scoper/TraverserFactory.php | 45 ++++++++++++- .../NativeTraverserFactory.php | 67 ------------------- src/functions.php | 4 +- tests/Scoper/PhpScoperTest.php | 10 +-- 5 files changed, 49 insertions(+), 81 deletions(-) delete mode 100644 src/Scoper/TraverserFactory/NativeTraverserFactory.php diff --git a/src/Scoper/PhpScoper.php b/src/Scoper/PhpScoper.php index 5b600abe..c3b741c0 100644 --- a/src/Scoper/PhpScoper.php +++ b/src/Scoper/PhpScoper.php @@ -32,11 +32,11 @@ final class PhpScoper implements Scoper private $decoratedScoper; private $traverserFactory; - public function __construct(Parser $parser, Scoper $decoratedScoper, TraverserFactory $traverserFactory) + public function __construct(Parser $parser, Scoper $decoratedScoper) { $this->parser = $parser; $this->decoratedScoper = $decoratedScoper; - $this->traverserFactory = $traverserFactory; + $this->traverserFactory = new TraverserFactory(); } /** diff --git a/src/Scoper/TraverserFactory.php b/src/Scoper/TraverserFactory.php index ad6938c0..07a32e74 100644 --- a/src/Scoper/TraverserFactory.php +++ b/src/Scoper/TraverserFactory.php @@ -14,10 +14,25 @@ namespace Humbug\PhpScoper\Scoper; +use Humbug\PhpScoper\NodeVisitor; +use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; +use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; +use PhpParser\NodeTraverser; use PhpParser\NodeTraverserInterface; -interface TraverserFactory +final class TraverserFactory { + /** + * Functions for which the arguments will be prefixed. + */ + const WHITELISTED_FUNCTIONS = [ + 'class_exists', + 'interface_exists', + ]; + + private $traverser; + /** * @param string $prefix Prefix to apply to the files. * @param string[] $whitelist List of classes to exclude from the scoping. @@ -27,5 +42,31 @@ interface TraverserFactory * * @return NodeTraverserInterface */ - public function create(string $prefix, array $whitelist, callable $globalWhitelister): NodeTraverserInterface; + public function create(string $prefix, array $whitelist, callable $globalWhitelister): NodeTraverserInterface + { + if (null !== $this->traverser) { + return $this->traverser; + } + + $this->traverser = new NodeTraverser(); + + $namespaceStatements = new NamespaceStmtCollection(); + $useStatements = new UseStmtCollection(); + + $nameResolver = new FullyQualifiedNameResolver($namespaceStatements, $useStatements); + + $this->traverser->addVisitor(new NodeVisitor\UseStmt\GroupUseStmtTransformer()); + + $this->traverser->addVisitor(new NodeVisitor\AppendParentNode()); + + $this->traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $namespaceStatements)); + + $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements)); + $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $globalWhitelister)); + + $this->traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); + $this->traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); + + return $this->traverser; + } } diff --git a/src/Scoper/TraverserFactory/NativeTraverserFactory.php b/src/Scoper/TraverserFactory/NativeTraverserFactory.php deleted file mode 100644 index d61923a6..00000000 --- a/src/Scoper/TraverserFactory/NativeTraverserFactory.php +++ /dev/null @@ -1,67 +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\Scoper\TraverserFactory; - -use Humbug\PhpScoper\NodeVisitor; -use Humbug\PhpScoper\NodeVisitor\Collection\NamespaceStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Collection\UseStmtCollection; -use Humbug\PhpScoper\NodeVisitor\Resolver\FullyQualifiedNameResolver; -use Humbug\PhpScoper\Scoper\TraverserFactory; -use PhpParser\NodeTraverser; -use PhpParser\NodeTraverserInterface; - -final class NativeTraverserFactory implements TraverserFactory -{ - /** - * Functions for which the arguments will be prefixed. - */ - const WHITELISTED_FUNCTIONS = [ - 'class_exists', - 'interface_exists', - ]; - - private $traverser; - - /** - * @inheritdoc - */ - public function create(string $prefix, array $whitelist, callable $globalWhitelister): NodeTraverserInterface - { - if (null !== $this->traverser) { - return $this->traverser; - } - - $this->traverser = new NodeTraverser(); - - $namespaceStatements = new NamespaceStmtCollection(); - $useStatements = new UseStmtCollection(); - - $nameResolver = new FullyQualifiedNameResolver($namespaceStatements, $useStatements); - - $this->traverser->addVisitor(new NodeVisitor\UseStmt\GroupUseStmtTransformer()); - - $this->traverser->addVisitor(new NodeVisitor\AppendParentNode()); - - $this->traverser->addVisitor(new NodeVisitor\NamespaceStmtPrefixer($prefix, $namespaceStatements)); - - $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtCollector($namespaceStatements, $useStatements)); - $this->traverser->addVisitor(new NodeVisitor\UseStmt\UseStmtPrefixer($prefix, $whitelist, $globalWhitelister)); - - $this->traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); - $this->traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $globalWhitelister, $nameResolver)); - - return $this->traverser; - } -} diff --git a/src/functions.php b/src/functions.php index 31a68594..b257184e 100644 --- a/src/functions.php +++ b/src/functions.php @@ -23,7 +23,6 @@ use Humbug\PhpScoper\Scoper\NullScoper; use Humbug\PhpScoper\Scoper\PatchScoper; use Humbug\PhpScoper\Scoper\PhpScoper; -use Humbug\PhpScoper\Scoper\TraverserFactory\NativeTraverserFactory; use Humbug\SelfUpdate\Exception\RuntimeException as SelfUpdateRuntimeException; use Humbug\SelfUpdate\Updater; use PackageVersions\Versions; @@ -101,8 +100,7 @@ function create_scoper(): Scoper new InstalledPackagesScoper( new PhpScoper( create_parser(), - new NullScoper(), - new NativeTraverserFactory() + new NullScoper() ) ) ) diff --git a/tests/Scoper/PhpScoperTest.php b/tests/Scoper/PhpScoperTest.php index a2d5c03d..d16ed9cb 100644 --- a/tests/Scoper/PhpScoperTest.php +++ b/tests/Scoper/PhpScoperTest.php @@ -17,7 +17,6 @@ use Generator; use Humbug\PhpScoper\PhpParser\FakeParser; use Humbug\PhpScoper\Scoper; -use Humbug\PhpScoper\Scoper\TraverserFactory\NativeTraverserFactory; use PhpParser\Error as PhpParserError; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -73,8 +72,7 @@ public function setUp() { $this->scoper = new PhpScoper( create_parser(), - new FakeScoper(), - new NativeTraverserFactory() + new FakeScoper() ); if (null === $this->tmp) { @@ -145,8 +143,7 @@ public function test_does_not_scope_file_if_is_not_a_PHP_file() $scoper = new PhpScoper( new FakeParser(), - $this->decoratedScoper, - new NativeTraverserFactory() + $this->decoratedScoper ); $actual = $scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister); @@ -219,8 +216,7 @@ public function test_does_not_scope_a_non_PHP_binary_files() $scoper = new PhpScoper( new FakeParser(), - $this->decoratedScoper, - new NativeTraverserFactory() + $this->decoratedScoper ); $actual = $scoper->scope($filePath, $prefix, $patchers, $whitelist, $whitelister);