Implement @method annotation indexation
continuous-integration/drone/push Build is passing Details

Hugo Thunnissen 7 months ago
parent 350850c07a
commit e65b268cea

@ -36,12 +36,6 @@
(cadr scope))
(t nil)))
(defun phpinspect-var-annotation-p (token)
(phpinspect-token-type-p token :var-annotation))
(defun phpinspect-return-annotation-p (token)
(phpinspect-token-type-p token :return-annotation))
(defun phpinspect--index-function-arg-list (type-resolver arg-list &optional add-used-types)
(let ((arg-index)
@ -154,7 +148,36 @@ function (think \"new\" statements, return types etc.)."
(cadr class-token))))
(cadr subtoken)))
(defun phpinspect--index-class (imports type-resolver location-resolver class)
(defsubst phpinspect--index-method-annotations (type-resolver comment)
(let ((annotations (seq-filter #'phpinspect-method-annotation-p comment))
(dolist (annotation annotations)
(let ((return-type) (name) (arg-list))
(when (> (length annotation) 2)
(cond ((and (phpinspect-word-p (nth 1 annotation))
(phpinspect-word-p (nth 2 annotation))
(phpinspect-list-p (nth 3 annotation)))
(setq return-type (cadr (nth 1 annotation)))
(setq name (cadr (nth 2 annotation)))
(setq arg-list (nth 3 annotation)))
((and (phpinspect-word-p (nth 1 annotation))
(phpinspect-list-p (nth 2 annotation)))
(setq return-type "void")
(setq name (cadr (nth 1 annotation)))
(setq arg-list (nth 2 annotation))))
(when name
(push (phpinspect--make-function
:scope '(:public)
:name name
:return-type (funcall type-resolver (phpinspect--make-type :name return-type))
:arguments (phpinspect--index-function-arg-list type-resolver arg-list))
(defun phpinspect--index-class (imports type-resolver location-resolver class &optional doc-block)
"Create an alist with relevant attributes of a parsed class."
(phpinspect--log "INDEXING CLASS")
(let ((methods)
@ -291,6 +314,12 @@ function (think \"new\" statements, return types etc.)."
(setf (phpinspect--variable-type variable)
(funcall type-resolver constructor-parameter-type))))))))
;; Add method annotations to methods
(when doc-block
(setq methods
(nconc methods (phpinspect--index-method-annotations type-resolver doc-block))))
(let ((class-name (funcall type-resolver (phpinspect--make-type :name class-name))))
`(,class-name .
@ -314,20 +343,23 @@ Accounts for namespaces that are defined with '{}' blocks."
(cdaddr namespace)
(cdr namespace)))
(defun phpinspect--index-classes
(imports classes type-resolver-factory location-resolver &optional namespace indexed)
"Index the class tokens in `classes`, using the imports in `imports`
as Fully Qualified names. `namespace` will be assumed the root
namespace if not provided"
(if classes
(let ((class (pop classes)))
(push (phpinspect--index-class
imports (funcall type-resolver-factory imports class namespace)
location-resolver class)
(phpinspect--index-classes imports classes type-resolver-factory
location-resolver namespace indexed))
(nreverse indexed)))
(defun phpinspect--index-classes-in-tokens
(imports tokens type-resolver-factory location-resolver &optional namespace indexed)
"Index the class tokens among TOKENS.
NAMESPACE will be assumed the root namespace if not provided"
(let ((comment-before)
(dolist (token tokens)
(cond ((phpinspect-doc-block-p token)
(setq comment-before token))
((phpinspect-class-p token)
(push (phpinspect--index-class
imports (funcall type-resolver-factory imports token namespace)
location-resolver token comment-before)
(setq comment-before nil))))
(defun phpinspect--use-to-type (use)
(let* ((fqn (cadr (cadr use)))
@ -346,9 +378,9 @@ namespace if not provided"
(mapcar #'phpinspect--use-to-type uses))
(defun phpinspect--index-namespace (namespace type-resolver-factory location-resolver)
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
(seq-filter #'phpinspect-class-p namespace)
type-resolver-factory location-resolver (cadadr namespace) nil))
(defun phpinspect--index-namespaces
@ -414,11 +446,8 @@ Return value is a list of the types that are \"newed\"."
(phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-p tokens)
(seq-filter #'phpinspect-class-p tokens)
imports tokens type-resolver-factory location-resolver)))
,(append '(used-types)
(phpinspect--find-used-types-in-tokens tokens))

@ -173,6 +173,18 @@ Type can be any of the token types returned by
"Get the argument list of a function"
(seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil))
(defun phpinspect-annotation-p (token)
(phpinspect-token-type-p token :annotation))
(defun phpinspect-method-annotation-p (token)
(phpinspect-token-type-p token :method-annotation))
(defun phpinspect-var-annotation-p (token)
(phpinspect-token-type-p token :var-annotation))
(defun phpinspect-return-annotation-p (token)
(phpinspect-token-type-p token :return-annotation))
(defsubst phpinspect-variable-p (token)
(phpinspect-token-type-p token :variable))
@ -429,16 +441,23 @@ token is \";\", which marks the end of a statement in PHP."
(defsubst phpinspect--parse-annotation-parameters (parameter-amount)
(let* ((words)
(list-handler (phpinspect-handler 'list))
(list-regexp (phpinspect-handler-regexp 'list))
(word-handler (phpinspect-handler 'word))
(word-regexp (phpinspect-handler-regexp 'word))
(variable-handler (phpinspect-handler 'variable))
(variable-regexp (phpinspect-handler-regexp 'variable)))
(while (not (or (looking-at "\\*/") (= (length words) parameter-amount)))
(cond ((looking-at word-regexp)
(variable-regexp (phpinspect-handler-regexp 'variable))
(annotation-regexp (phpinspect-handler-regexp 'annotation)))
(while (not (or (looking-at annotation-regexp)
(= (point) (point-max))
(= (length words) parameter-amount)))
(cond ((looking-at list-regexp)
(push (funcall list-handler (match-string 0) (point-max)) words))
((looking-at word-regexp)
(push (funcall word-handler (match-string 0)) words))
((looking-at variable-regexp)
(push (funcall variable-handler (match-string 0)) words))))
(push (funcall variable-handler (match-string 0)) words))
(t (forward-char))))
(nreverse words)))
(phpinspect-defhandler annotation (start-token &rest _ignored)
@ -463,6 +482,9 @@ token is \";\", which marks the end of a statement in PHP."
;; The type of the param, and the param's $name
(append (list :param-annotation)
(phpinspect--parse-annotation-parameters 2)))
((string= annotation-name "method")
(append (list :method-annotation)
(phpinspect--parse-annotation-parameters 3)))
(list :annotation annotation-name))))
(list :annotation nil)))
@ -481,11 +503,21 @@ token is \";\", which marks the end of a statement in PHP."
(forward-char (length start-token))
(cond ((string-match "/\\*" start-token)
(let* ((continue-condition (lambda () (not (looking-at "\\*/"))))
(let* ((region-start (point))
;; Move to the end of the comment region
(while (not (or (= max-point (point)) (looking-at "\\*/")))
(comment-contents (buffer-substring region-start region-end))
(parser (phpinspect-get-parser-create
'(annotation whitespace)))
(doc-block (funcall parser (current-buffer) max-point continue-condition)))
(doc-block (with-temp-buffer
(insert comment-contents)
(goto-char (point-min))
(funcall parser (current-buffer) (point-max)))))
(forward-char 2)

@ -1 +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") (:terminator ";")) (:use (:word "Doctrine\\ORM\\EntityManagerInterface") (:terminator ";")) (:use (:word "Twig\\Environment") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\Request") (:terminator ";")) (:use (:word "Symfony\\Component\\Routing\\Annotation\\Route") (:terminator ";")) (:word "class") (:word "AddressController") (:incomplete-block (:const (:word "A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE") (:assignment "=") (:string "a value") (:terminator ";")) (:public (:const (:word "ARRAY_CONSTANT") (:assignment "=") (:array (:string "key") (:fat-arrow "=>") (:string "value") (:comma ",") (:string "key") (:fat-arrow "=>")) (:terminator ";"))) (:private (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil)))))))))
(: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") (:terminator ";")) (:use (:word "Doctrine\\ORM\\EntityManagerInterface") (:terminator ";")) (:use (:word "Twig\\Environment") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\Request") (:terminator ";")) (:use (:word "Symfony\\Component\\Routing\\Annotation\\Route") (:terminator ";")) (:class (:declaration (:word "class") (:word "AddressController")) (:incomplete-block (:const (:word "A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE") (:assignment "=") (:string "a value") (:terminator ";")) (:public (:const (:word "ARRAY_CONSTANT") (:assignment "=") (:array (:string "key") (:fat-arrow "=>") (:string "value") (:comma ",") (:string "key") (:fat-arrow "=>")) (:terminator ";"))) (:private (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil))))))))))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -13,7 +13,15 @@ use Twig\Environment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class AddressController
* @method int holdUp(Something $thing)
* gaerfawe awjfawijef;aw';ajef0()Eawea
* @method Potato holdUp(OtherThing $thing)
* (afwa fae $eafw)
* @method noReturnType(Thing $thing)
class AddressController
public const ARRAY_CONSTANT = [

@ -242,47 +242,6 @@
(funcall type-resolver (phpinspect--make-type
:name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-index-static-methods ()
(let* ((class-tokens
(:declaration (:word "class") (:word "Potato"))
(:function (:declaration (:word "function")
(:word "staticMethod")
(:list (:variable "untyped")
(:word "array")
(:variable "things")))
(index (phpinspect--index-tokens class-tokens))
(,(phpinspect--make-type :name"\\Potato" :fully-qualified t)
(class-name . ,(phpinspect--make-type :name "\\Potato" :fully-qualified t))
(static-methods . (,(phpinspect--make-function
:name "staticMethod"
:scope '(:public)
:arguments `(("untyped" nil)
("things" ,(phpinspect--make-type :name "\\array"
:fully-qualified t)))
:return-type phpinspect--null-type)))
;; (pp expected-index)
;; (pp index))
(should (equal expected-index index))))
(ert-deftest phpinspect-resolve-type-from-context ()
(let* ((token-tree (phpinspect-parse-string "
namespace Amazing;

@ -49,11 +49,11 @@ populated when the variable is set and the data in it is accurate."
(should class-region)
(should classname-region)
;; Character position of the start of the class token.
(should (= 417 (phpinspect-region-start class-region)))
(should (= 2173 (phpinspect-region-end class-region)))
(should (= 611 (phpinspect-region-start class-region)))
(should (= 2367 (phpinspect-region-end class-region)))
(should (= 423 (phpinspect-region-start classname-region)))
(should (= 440 (phpinspect-region-end classname-region))))))
(should (= 617 (phpinspect-region-start classname-region)))
(should (= 634 (phpinspect-region-end classname-region))))))
(ert-deftest phpinspect-parse-buffer-no-current ()
"Confirm that the parser is still functional with

@ -101,3 +101,45 @@ return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->anti
(dolist (set blocks)
(let ((result (phpinspect--find-used-types-in-tokens (car set))))
(should (equal (cadr set) result))))))
(ert-deftest phpinspect-index-method-annotations ()
(let* ((result (phpinspect--index-tokens
/* @method int peel(bool $fast, array $loose)
* @method Banana duplicate()
@method hold() **/
class Banana {}")))
(class (car (alist-get 'classes result)))
(methods (alist-get 'methods class)))
(should (= 3 (length methods)))
(dolist (method methods)
(should (member (phpinspect--function-name method)
'("duplicate" "hold" "peel")))
(cond ((string= (phpinspect--function-name method)
(should (phpinspect--type=
(phpinspect--make-type :name "\\Banana" :fully-qualified t)
(phpinspect--function-return-type method))))
((string= (phpinspect--function-name method)
(should (phpinspect--type=
(phpinspect--make-type :name "\\int" :fully-qualified t)
(phpinspect--function-return-type method)))
(should (= 2 (length (phpinspect--function-arguments method))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\array" :fully-qualified t)
(car (alist-get
"loose" (phpinspect--function-arguments method) nil nil #'string=))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\bool" :fully-qualified t)
(car (alist-get
"fast" (phpinspect--function-arguments method) nil nil #'string=)))))
((string= (phpinspect--function-name method)
(should (phpinspect--type=
(phpinspect--make-type :name "\\void" :fully-qualified t)
(phpinspect--function-return-type method))))))))