branch: externals/phpinspect
commit e65b268cea0b3d7c0e93b57c0621608c5404efec
Author: Hugo Thunnissen <de...@hugot.nl>
Commit: Hugo Thunnissen <de...@hugot.nl>

    Implement @method annotation indexation
---
 phpinspect-index.el                                | 85 +++++++++++++++-------
 phpinspect-parser.el                               | 46 ++++++++++--
 test/fixtures/IncompleteClassBlockedNamespace.eld  |  2 +-
 .../fixtures/IncompleteClassMultipleNamespaces.eld |  2 +-
 test/fixtures/NamespacedClass.eld                  |  2 +-
 test/fixtures/NamespacedClass.php                  | 10 ++-
 test/phpinspect-test.el                            | 41 -----------
 test/test-buffer.el                                |  8 +-
 test/test-index.el                                 | 42 +++++++++++
 9 files changed, 154 insertions(+), 84 deletions(-)

diff --git a/phpinspect-index.el b/phpinspect-index.el
index c952c4e625..f45a5b4900 100644
--- a/phpinspect-index.el
+++ b/phpinspect-index.el
@@ -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)
         (current-token)
@@ -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))
+        (methods))
+    (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))
+                  methods)))))
+    methods))
+
+
+(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 .
                     (phpinspect--indexed-class
@@ -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)
-              indexed)
-        (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)
+        (indexed))
+    (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)
+                   indexed)
+             (setq comment-before nil))))
+    indexed))
 
 (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--index-classes
+  (phpinspect--index-classes-in-tokens
    (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
-   (seq-filter #'phpinspect-class-p namespace)
+   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)
                                               type-resolver-factory
                                               location-resolver)
-                (phpinspect--index-classes
-                 imports
-                 (seq-filter #'phpinspect-class-p tokens)
-                 type-resolver-factory
-                 location-resolver)))
+                (phpinspect--index-classes-in-tokens
+                 imports tokens type-resolver-factory location-resolver)))
       ,(append '(used-types)
                (phpinspect--find-used-types-in-tokens tokens))
       (functions))
diff --git a/phpinspect-parser.el b/phpinspect-parser.el
index 8471a908e1..1265a7691f 100644
--- a/phpinspect-parser.el
+++ b/phpinspect-parser.el
@@ -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)))
-      (forward-char)
-      (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)))
               (t
                (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
+                (region-end
+                 (progn
+                   (while (not (or (= max-point (point)) (looking-at "\\*/")))
+                     (forward-char))
+                   (point)))
+                (comment-contents (buffer-substring region-start region-end))
                 (parser (phpinspect-get-parser-create
                          :doc-block
                          '(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)
            doc-block))
         (t
diff --git a/test/fixtures/IncompleteClassBlockedNamespace.eld 
b/test/fixtures/IncompleteClassBlockedNamespace.eld
index bb7e112c81..2bf9eaa99a 100644
--- a/test/fixtures/IncompleteClassBlockedNamespace.eld
+++ b/test/fixtures/IncompleteClassBlockedNamespace.eld
@@ -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") ( [...]
\ No newline at end of file
+(: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/IncompleteClassMultipleNamespaces.eld 
b/test/fixtures/IncompleteClassMultipleNamespaces.eld
index 7d333155b3..f978752445 100644
--- a/test/fixtures/IncompleteClassMultipleNamespaces.eld
+++ b/test/fixtures/IncompleteClassMultipleNamespaces.eld
@@ -1 +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
+(: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/NamespacedClass.eld 
b/test/fixtures/NamespacedClass.eld
index c293769441..f536a1abe0 100644
--- a/test/fixtures/NamespacedClass.eld
+++ b/test/fixtures/NamespacedClass.eld
@@ -1 +1 @@
-(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Controller") (:terminator ";") (: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
+(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Controller") (:terminator ";") (: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/NamespacedClass.php 
b/test/fixtures/NamespacedClass.php
index 2ad5938cd3..5b50e09b9b 100644
--- a/test/fixtures/NamespacedClass.php
+++ b/test/fixtures/NamespacedClass.php
@@ -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
 {
     const A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE = 'a value';
     public const ARRAY_CONSTANT = [
diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el
index 0c48f2c1ee..b1c531f5e4 100644
--- a/test/phpinspect-test.el
+++ b/test/phpinspect-test.el
@@ -242,47 +242,6 @@
                                (funcall type-resolver (phpinspect--make-type
                                                        :name 
"Dupuis\\GastonLagaffe"))))))
 
-(ert-deftest phpinspect-index-static-methods ()
-  (let* ((class-tokens
-          `(:root
-            (:class
-             (:declaration (:word "class") (:word "Potato"))
-             (:block
-              (:static
-               (:function (:declaration (:word "function")
-                                        (:word "staticMethod")
-                                        (:list (:variable "untyped")
-                                               (:comma)
-                                               (:word "array")
-                                               (:variable "things")))
-                          (:block)))))))
-         (index (phpinspect--index-tokens class-tokens))
-         (expected-index
-          `(phpinspect--root-index
-            (imports)
-            (classes
-             (,(phpinspect--make-type :name"\\Potato" :fully-qualified t)
-              phpinspect--indexed-class
-              (class-name . ,(phpinspect--make-type :name "\\Potato" 
:fully-qualified t))
-              (imports)
-              (methods)
-              (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)))
-              (static-variables)
-              (variables)
-              (constants)
-              (extends)
-              (implements)))
-            (functions))))
-    ;; (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;
diff --git a/test/test-buffer.el b/test/test-buffer.el
index ed36ae522f..b50f1bb4be 100644
--- a/test/test-buffer.el
+++ b/test/test-buffer.el
@@ -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
diff --git a/test/test-index.el b/test/test-index.el
index 2d1e37d288..3fdfb0044d 100644
--- a/test/test-index.el
+++ b/test/test-index.el
@@ -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
+                  (phpinspect-parse-string
+                   "<?php
+
+/* @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)
+                      "duplicate")
+             (should (phpinspect--type=
+                      (phpinspect--make-type :name "\\Banana" :fully-qualified 
t)
+                      (phpinspect--function-return-type method))))
+            ((string= (phpinspect--function-name method)
+                      "peel")
+             (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)
+                      "hold")
+             (should (phpinspect--type=
+                      (phpinspect--make-type :name "\\void" :fully-qualified t)
+                      (phpinspect--function-return-type method))))))))

Reply via email to