branch: externals/phpinspect commit faa936a4f0e99abb2a07c131ca6a23df40328ea1 Author: Hugo Thunnissen <de...@hugot.nl> Commit: Hugo Thunnissen <de...@hugot.nl>
Test type-resolver + make type resolving work for "blocked" namespaces Test type-resolver created from resolvecontext with: - File containing single namespace and no block - File containing one namespace with a block ('{}') - File containing multiple namespaces with blocks Patch `phpinspect--make-type-resolver-for-resolvecontext' to support namespaces with blocks. --- phpinspect.el | 19 ++- test/fixtures/IncompleteClassBlockedNamespace.eld | 1 + test/fixtures/IncompleteClassBlockedNamespace.php | 81 ++++++++++ .../fixtures/IncompleteClassMultipleNamespaces.eld | 1 + .../fixtures/IncompleteClassMultipleNamespaces.php | 164 +++++++++++++++++++++ test/phpinspect-test.el | 55 ++++++- test/util/generate-test-data.el | 4 +- 7 files changed, 318 insertions(+), 7 deletions(-) diff --git a/phpinspect.el b/phpinspect.el index 7743212963..23b7be484b 100644 --- a/phpinspect.el +++ b/phpinspect.el @@ -1513,7 +1513,8 @@ said FQN's by class name" (concat "\\" namespace "\\" type)) ;; Clas|interface|trait name - (t (concat "\\" (or (assoc-default type types #'string=) (concat namespace "\\" type)))))) + (t (concat "\\" (or (assoc-default type types #'string=) + (concat namespace "\\" type)))))) (defun phpinspect-var-annotation-p (token) (phpinspect-type-p token :var-annotation)) @@ -1702,6 +1703,12 @@ said FQN's by class name" (extends . ,extends) (implements . ,implements)))))) +(defsubst phpinspect-namespace-body (namespace) + "Return the nested tokens in NAMESPACE tokens' body. +Accounts for namespaces that are defined with '{}' blocks." + (if (phpinspect-block-p (caddr namespace)) + (cdaddr namespace) + (cdr namespace))) (defun phpinspect--index-classes (types classes &optional namespace indexed) "Index the class tokens in `classes`, using the types in `types` @@ -2087,15 +2094,19 @@ static variables and static methods." (let ((namespace-or-root (seq-find #'phpinspect-namespace-or-root-p (phpinspect--resolvecontext-enclosing-tokens - resolvecontext)))) + resolvecontext))) + (namespace-name)) + (when (phpinspect-namespace-p namespace-or-root) + (setq namespace-name (cadadr namespace-or-root)) + (setq namespace-or-root (phpinspect-namespace-body namespace-or-root))) + (phpinspect--make-type-resolver (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace-or-root)) (seq-find #'phpinspect-class-p (phpinspect--resolvecontext-enclosing-tokens resolvecontext)) - (when (phpinspect-namespace-p namespace-or-root) - (cadadr namespace-or-root))))) + namespace-name))) (defun phpinspect--get-last-statement-in-token (token) (setq token (cond ((phpinspect-function-p token) diff --git a/test/fixtures/IncompleteClassBlockedNamespace.eld b/test/fixtures/IncompleteClassBlockedNamespace.eld new file mode 100644 index 0000000000..bb7e112c81 --- /dev/null +++ b/test/fixtures/IncompleteClassBlockedNamespace.eld @@ -0,0 +1 @@ +(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Controller") (:incomplete-block (:use (:word "Symfony\\Component\\HttpFoundation\\Response") (:terminator ";")) (:use (:word "App\\Entity\\Address") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\RedirectResponse") (:terminator ";")) (:use (:word "App\\Repository\\AddressRepository") (:terminator ";")) (:use (:word "App\\Repository\\UserRepository") ( [...] \ No newline at end of file diff --git a/test/fixtures/IncompleteClassBlockedNamespace.php b/test/fixtures/IncompleteClassBlockedNamespace.php new file mode 100644 index 0000000000..43c133c468 --- /dev/null +++ b/test/fixtures/IncompleteClassBlockedNamespace.php @@ -0,0 +1,81 @@ +<?php +declare(strict_types=1); + +namespace App\Controller { + + use Symfony\Component\HttpFoundation\Response; + use App\Entity\Address; + use Symfony\Component\HttpFoundation\RedirectResponse; + use App\Repository\AddressRepository; + use App\Repository\UserRepository; + use Doctrine\ORM\EntityManagerInterface; + use Twig\Environment; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Annotation\Route; + + class AddressController + { + const A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE = 'a value'; + public const ARRAY_CONSTANT = [ + 'key' => 'value', + 'key' => 0 + ]; + + private $repo; + private $user_repo; + private $twig; + private $em; + + public function __construct( + AddressRepository $repo, + UserRepository $user_repo, + Environment $twig, + EntityManagerInterface $em + ) { + $this->repo = $repo; + $this->user_repo = $user_repo; + $this->twig = $twig; + $this->em = $em; + } + + /** + * @Route("/address/add", methods={"GET"}) + */ + public function addAddressPage(Request $req): Response + { + $user = $this->user_repo->findOne($req->get('user')); + + return new Response( + $this->twig->render('address/create.html.twig', [ + 'user' => $user, + ]) + ); + } + + /** + * @Route("/address/add", methods={"POST"}) + */ + public function addAddressAction(Request $req): Response + { + $user = $this->user_repo->findOne($req->request->get('user')); + $address_string = $req->request->get('address'); + + $address = new Address($user, $address_string); + + $this->em->persist($address); + $this->em->flush(); + + + return new RedirectResponse('/user/' . $user->getLoginName() . '/manage'); + } + + /** + * @Route("/address/delete", methods={"POST"}) + */ + public function deleteAddressAction(Request $req): Response + { + $address = $this->repo->find($req->request->get('address')); + + // This is what a while looks like to phpinspect when it parses up + // until "point" to complete. + $this->em->remove($this->em-> diff --git a/test/fixtures/IncompleteClassMultipleNamespaces.eld b/test/fixtures/IncompleteClassMultipleNamespaces.eld new file mode 100644 index 0000000000..7d333155b3 --- /dev/null +++ b/test/fixtures/IncompleteClassMultipleNamespaces.eld @@ -0,0 +1 @@ +(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "Circus\\Artist") (:block (:use (:word "Symfony\\Component\\HttpFoundation\\Response") (:terminator ";")) (:use (:word "App\\Entity\\Address") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\RedirectResponse") (:terminator ";")) (:use (:word "App\\Repository\\AddressRepository") (:terminator ";")) (:use (:word "App\\Repository\\UserRepository") (:terminator [...] \ No newline at end of file diff --git a/test/fixtures/IncompleteClassMultipleNamespaces.php b/test/fixtures/IncompleteClassMultipleNamespaces.php new file mode 100644 index 0000000000..15a705741d --- /dev/null +++ b/test/fixtures/IncompleteClassMultipleNamespaces.php @@ -0,0 +1,164 @@ +<?php +declare(strict_types=1); + +namespace Circus\Artist { + + use Symfony\Component\HttpFoundation\Response; + use App\Entity\Address; + use Symfony\Component\HttpFoundation\RedirectResponse; + use App\Repository\AddressRepository; + use App\Repository\UserRepository; + use Doctrine\ORM\EntityManagerInterface; + use Twig\Environment; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Annotation\Route; + + class AddressController + { + const A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE = 'a value'; + public const ARRAY_CONSTANT = [ + 'key' => 'value', + 'key' => 0 + ]; + + private $repo; + private $user_repo; + private $twig; + private $em; + + public function __construct( + AddressRepository $repo, + UserRepository $user_repo, + Environment $twig, + EntityManagerInterface $em + ) { + $this->repo = $repo; + $this->user_repo = $user_repo; + $this->twig = $twig; + $this->em = $em; + } + + /** + * @Route("/address/add", methods={"GET"}) + */ + public function addAddressPage(Request $req): Response + { + $user = $this->user_repo->findOne($req->get('user')); + + return new Response( + $this->twig->render('address/create.html.twig', [ + 'user' => $user, + ]) + ); + } + + /** + * @Route("/address/add", methods={"POST"}) + */ + public function addAddressAction(Request $req): Response + { + $user = $this->user_repo->findOne($req->request->get('user')); + $address_string = $req->request->get('address'); + + $address = new Address($user, $address_string); + + $this->em->persist($address); + $this->em->flush(); + + + return new RedirectResponse('/user/' . $user->getLoginName() . '/manage'); + } + + /** + * @Route("/address/delete", methods={"POST"}) + */ + public function deleteAddressAction(Request $req): Response + { + $address = $this->repo->find($req->request->get('address')); + + $this->em->remove($address); + $this->em->flush(); + + return new RedirectResponse('/user/' . $address->getUser()->getLoginName() . '/manage'); + } + } +} + +namespace App\Controller { + + use Symfony\Component\HttpFoundation\Response; + use App\Entity\Address; + use Symfony\Component\HttpFoundation\RedirectResponse; + use App\Repository\AddressRepository; + use App\Repository\UserRepository; + use Doctrine\ORM\EntityManagerInterface; + use Twig\Environment; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Annotation\Route; + + class AddressController + { + const A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE = 'a value'; + public const ARRAY_CONSTANT = [ + 'key' => 'value', + 'key' => 0 + ]; + + private $repo; + private $user_repo; + private $twig; + private $em; + + public function __construct( + AddressRepository $repo, + UserRepository $user_repo, + Environment $twig, + EntityManagerInterface $em + ) { + $this->repo = $repo; + $this->user_repo = $user_repo; + $this->twig = $twig; + $this->em = $em; + } + + /** + * @Route("/address/add", methods={"GET"}) + */ + public function addAddressPage(Request $req): Response + { + $user = $this->user_repo->findOne($req->get('user')); + + return new Response( + $this->twig->render('address/create.html.twig', [ + 'user' => $user, + ]) + ); + } + + /** + * @Route("/address/add", methods={"POST"}) + */ + public function addAddressAction(Request $req): Response + { + $user = $this->user_repo->findOne($req->request->get('user')); + $address_string = $req->request->get('address'); + + $address = new Address($user, $address_string); + + $this->em->persist($address); + $this->em->flush(); + + + return new RedirectResponse('/user/' . $user->getLoginName() . '/manage'); + } + + /** + * @Route("/address/delete", methods={"POST"}) + */ + public function deleteAddressAction(Request $req): Response + { + $address = $this->repo->find($req->request->get('address')); + + // This is what a while looks like to phpinspect when it parses up + // until "point" to complete. + $this->em->remove($this->em-> diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el index 8cdb154c9b..44c93668b5 100644 --- a/test/phpinspect-test.el +++ b/test/phpinspect-test.el @@ -127,7 +127,7 @@ (phpinspect-test-read-fixture-data "class-index-1-2-undestructive-merge")))) -(ert-deftest phpinspect-find-innermost-incomplete-nested-token () +(ert-deftest phpinspect--get-resolvecontext () (let ((resolvecontext (phpinspect--get-resolvecontext (phpinspect-test-read-fixture-data "IncompleteClass")))) @@ -150,7 +150,58 @@ (should (phpinspect-incomplete-class-p (cadddr (phpinspect--resolvecontext-enclosing-tokens - resolvecontext)))))) + resolvecontext)))))) + +(ert-deftest phpinspect-type-resolver-for-resolvecontext () + (let* ((resolvecontext (phpinspect--get-resolvecontext + (phpinspect-test-read-fixture-data "IncompleteClass"))) + (type-resolver (phpinspect--make-type-resolver-for-resolvecontext + resolvecontext))) + + (should (string= "\\array" (funcall type-resolver "array"))) + (should (string= "\\array" (funcall type-resolver "\\array"))) + (should (string= "\\Symfony\\Component\\HttpFoundation\\Response" + (funcall type-resolver "Response"))) + (should (string= "\\Response" (funcall type-resolver "\\Response"))) + (should (string= "\\App\\Controller\\GastonLagaffe" + (funcall type-resolver "GastonLagaffe"))) + (should (string= "\\App\\Controller\\Dupuis\\GastonLagaffe" + (funcall type-resolver "Dupuis\\GastonLagaffe"))))) + +(ert-deftest phpinspect-type-resolver-for-resolvecontext-namespace-block () + (let* ((resolvecontext (phpinspect--get-resolvecontext + (phpinspect-test-read-fixture-data + "IncompleteClassBlockedNamespace"))) + (type-resolver (phpinspect--make-type-resolver-for-resolvecontext + resolvecontext))) + + (should (string= "\\array" (funcall type-resolver "array"))) + (should (string= "\\array" (funcall type-resolver "\\array"))) + (should (string= "\\Symfony\\Component\\HttpFoundation\\Response" + (funcall type-resolver "Response"))) + (should (string= "\\Response" (funcall type-resolver "\\Response"))) + (should (string= "\\App\\Controller\\GastonLagaffe" + (funcall type-resolver "GastonLagaffe"))) + (should (string= "\\App\\Controller\\Dupuis\\GastonLagaffe" + (funcall type-resolver "Dupuis\\GastonLagaffe"))))) + +(ert-deftest phpinspect-type-resolver-for-resolvecontext-multiple-namespace-blocks () + (let* ((resolvecontext (phpinspect--get-resolvecontext + (phpinspect-test-read-fixture-data + "IncompleteClassMultipleNamespaces"))) + (type-resolver (phpinspect--make-type-resolver-for-resolvecontext + resolvecontext))) + + (should (string= "\\array" (funcall type-resolver "array"))) + (should (string= "\\array" (funcall type-resolver "\\array"))) + (should (string= "\\Symfony\\Component\\HttpFoundation\\Response" + (funcall type-resolver "Response"))) + (should (string= "\\Response" (funcall type-resolver "\\Response"))) + (should (string= "\\App\\Controller\\GastonLagaffe" + (funcall type-resolver "GastonLagaffe"))) + (should (string= "\\App\\Controller\\Dupuis\\GastonLagaffe" + (funcall type-resolver "Dupuis\\GastonLagaffe"))))) + (provide 'phpinspect-test) ;;; phpinspect-test.el ends here diff --git a/test/util/generate-test-data.el b/test/util/generate-test-data.el index defed76e24..4da44047a0 100644 --- a/test/util/generate-test-data.el +++ b/test/util/generate-test-data.el @@ -3,7 +3,9 @@ (let ((here (file-name-directory (or load-file-name - buffer-file-name)))) + buffer-file-name))) + (print-length 1000) + (print-level 1000)) (dolist (file (directory-files (concat here "/../fixtures" ) t "\\.php$")) (with-temp-buffer (insert-file-contents-literally file)