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

    Add tests, fix bugs in reactive class property type indexation
---
 phpinspect-buffer.el           | 41 ++++++++++++++++++++++++--
 phpinspect-meta.el             | 22 ++++++++++++++
 phpinspect-property-cell.el    |  2 +-
 phpinspect-typedef.el          | 11 ++++---
 test/test-buffer-indexation.el | 65 ++++++++++++++++++++++++++++++++++++++++++
 test/test-buffer-parsing.el    | 51 +++++++++++++++++++++++++++++++++
 6 files changed, 185 insertions(+), 7 deletions(-)

diff --git a/phpinspect-buffer.el b/phpinspect-buffer.el
index 5896957ffa..452f65280b 100644
--- a/phpinspect-buffer.el
+++ b/phpinspect-buffer.el
@@ -146,7 +146,7 @@ linked with."
 (define-inline phpinspect--can-delete-buffer-index-for-token (token)
   (inline-quote
    (phpinspect-token-type-p
-    ,token :class-declaration :function-declaration :const :class-variable 
:function)))
+    ,token :class-declaration :function-declaration :const :class-variable 
:function :word)))
 
 (cl-defmethod phpinspect-buffer-delete-index-for-token ((buffer 
phpinspect-buffer) token)
   "Delete index entities that were derived from TOKEN.
@@ -193,12 +193,24 @@ tokens that have been deleted from a buffer."
              (if-let ((token-meta (phpinspect-buffer-token-meta buffer token)))
                  (phpi-typedef-delete-property-token-definition class 
(phpi-var-name (cdr var)) token-meta)
                (phpi-typedef-delete-property class (cdr var))))))
+        ((phpinspect-word-p token)
+         (phpinspect-buffer--delete-word-index-reference buffer token))
         ((or (phpinspect-this-p token) (phpinspect-attrib-p token))
          (phpinspect-buffer--delete-dynamic-prop-index-reference buffer token))
         ((phpinspect-function-p token)
          (phpinspect-buffer--delete-function-index-reference buffer token))
         (t (error "Cannot delete index for token %s" token))))
 
+(defun phpinspect-buffer--delete-word-index-reference (buffer token)
+  (when-let ((index-ref (gethash token (phpinspect-buffer-token-index 
buffer))))
+    (cond ((and (phpinspect--type-p (car index-ref))
+                (phpinspect-property-p (cdr index-ref)))
+           (when-let ((typedef (phpinspect-project-get-typedef
+                       (phpinspect-buffer-project buffer)
+                       (car index-ref))))
+           ;; Unset property type
+           (phpi-typedef-set-property-type typedef (phpi-prop-name (cdr 
index-ref)) nil))))))
+
 (defun phpinspect-buffer--delete-function-index-reference (buffer token)
   (when-let ((func (gethash token (phpinspect-buffer-token-index buffer))))
     (let ((arg-list (phpinspect-function-argument-list token)))
@@ -581,7 +593,18 @@ DECLARATION must be an object of type `phpinspect-meta'."
 
         (phpinspect-buffer-set-index-reference-for-token
          buffer (phpinspect-meta-token var)
-         (cons (phpi-typedef-name typedef) indexed))))))
+         (cons (phpi-typedef-name typedef) indexed))
+
+        ;; Add index reference for typehint if present
+        (when-let ((type-word (phpinspect-meta-find-left-sibling-matching-token
+                               var #'phpinspect-word-p)))
+          (phpinspect-buffer-set-index-reference-for-token
+           buffer (phpinspect-meta-token type-word)
+           (cons (phpi-typedef-name typedef) indexed)))))))
+
+(defun phpinspect-buffer--index-prop-type-word (buffer word)
+  (let ((variable (phpinspect-meta-find-right-sibling-matching-token word 
#'phpinspect-class-variable-p)))
+    (phpinspect-buffer--index-class-variable buffer variable)))
 
 (cl-defmethod phpinspect-buffer-update-project-index ((buffer 
phpinspect-buffer))
   "Update project index using the last parsed token map of this
@@ -952,6 +975,17 @@ SHADOW should be an instance of `phpinspect-shadow'."
          ((pred phpinspect-class-variable-p)
           (phpinspect-buffer--update-class-variable-fqn-type buffer token 
type)))))))
 
+(defun phpi-word-is-prop-type-and-should-be-indexed-p (word-meta additions)
+  (and-let* (
+             ;; Word is inside a scope token
+             ((phpinspect-scope-p (phpinspect-meta-token 
(phpinspect-meta-parent word-meta))))
+
+             ;; Word is followed by a class variable
+             (var (phpinspect-meta-find-right-sibling-matching-token word-meta 
#'phpinspect-class-variable-p))
+
+             ;; Var is not already going to be indexed
+             ((not (gethash (phpinspect-meta-token var) additions))))))
+
 (defun phpi-shadow-process-additions (shadow ctx)
   "Process newly added tokens for SHADOW using CTX state.
 
@@ -975,6 +1009,9 @@ SHADOW should be an instance of `phpinspect-shadow'."
           ((pred (phpinspect-use-p))
            (phpinspect-buffer--index-use buffer addition)
            (setf (phpi-sidc-imports-changed ctx) t))
+          ((pred phpinspect-word-p)
+           (when (phpi-word-is-prop-type-and-should-be-indexed-p addition 
additions)
+             (phpinspect-buffer--index-prop-type-word buffer addition)))
           ((or (pred phpinspect-class-variable-p)
                (pred phpinspect-const-p))
            (phpinspect-buffer--index-class-variable buffer addition)))))
diff --git a/phpinspect-meta.el b/phpinspect-meta.el
index 79ad87e1d1..0aa281a3fe 100644
--- a/phpinspect-meta.el
+++ b/phpinspect-meta.el
@@ -245,6 +245,17 @@ positions based on the parent)."
     (phpinspect-splayt-find-largest-before (phpinspect-meta-children 
(phpinspect-meta-parent meta))
                                            (phpinspect-meta-parent-offset 
meta))))
 
+(cl-defmethod phpinspect-meta-find-left-sibling-matching-token ((meta (head 
meta)) predicate)
+  (when-let ((parent (phpinspect-meta-parent meta)))
+    (catch 'phpinspect--return
+      (while (setq meta (phpinspect-splayt-find-largest-before 
(phpinspect-meta-children parent)
+                                                               
(phpinspect-meta-parent-offset meta)))
+        (when (funcall predicate (phpinspect-meta-token meta))
+          (throw 'phpinspect--return meta))
+
+        (unless (phpinspect-comment-p (phpinspect-meta-token meta))
+          (throw 'phpinspect--return nil))))))
+
 (define-inline phpinspect-meta-find-right-sibling (meta)
   (inline-letevals (meta)
     (inline-quote
@@ -252,6 +263,17 @@ positions based on the parent)."
        (phpinspect-splayt-find-smallest-after (phpinspect-meta-children 
(phpinspect-meta-parent ,meta))
                                               (phpinspect-meta-parent-offset 
,meta))))))
 
+(cl-defmethod phpinspect-meta-find-right-sibling-matching-token ((meta (head 
meta)) predicate)
+  (when-let ((parent (phpinspect-meta-parent meta)))
+    (catch 'phpinspect--return
+      (while (setq meta (phpinspect-splayt-find-smallest-after 
(phpinspect-meta-children parent)
+                                                               
(phpinspect-meta-parent-offset meta)))
+        (when (funcall predicate (phpinspect-meta-token meta))
+          (throw 'phpinspect--return meta))
+
+        (unless (phpinspect-comment-p (phpinspect-meta-token meta))
+          (throw 'phpinspect--return nil))))))
+
 (define-inline phpinspect-meta-find-overlapping-child (meta point)
   (inline-letevals (meta point)
     (inline-quote
diff --git a/phpinspect-property-cell.el b/phpinspect-property-cell.el
index 767bbeb30d..d18dac946c 100644
--- a/phpinspect-property-cell.el
+++ b/phpinspect-property-cell.el
@@ -42,7 +42,7 @@
            :type phpinspect-name)
   (definition-tokens nil)
   (definition nil
-             :type phpinspect--variable))
+                 :type phpinspect--variable))
 
 (defun phpinspect-make-property (origin-type definition)
   ;; A property cannot have a nil name.
diff --git a/phpinspect-typedef.el b/phpinspect-typedef.el
index 5ef1b3c5fb..ca3a43052d 100644
--- a/phpinspect-typedef.el
+++ b/phpinspect-typedef.el
@@ -456,6 +456,11 @@ them, which are then incorporated into DEF's properties."
   ((def phpinspect-typedef) (method-name (head phpinspect-name)) &optional 
tryhard)
   (phpi-mcol-get-return-type (phpi-typedef-static-methods def) method-name 
tryhard))
 
+(cl-defmethod phpi-typedef-set-property-type ((def phpinspect-typedef) 
prop-name type)
+  (phpi--typedef-edit def
+    (when-let ((prop (phpi-typedef-get-property def prop-name)))
+      (setf (phpi-prop-type prop) type))))
+
 (cl-defmethod phpi-typedef-set-property ((def phpinspect-typedef) (prop 
phpinspect-property))
   (phpi--typedef-edit def
     (phpi-pcol-add (phpi-typedef-properties def) prop)
@@ -468,8 +473,8 @@ them, which are then incorporated into DEF's properties."
     (phpi-typedef-set-property def prop)))
 
 (cl-defmethod phpi-typedef-delete-property ((def phpinspect-typedef)
-                                           (name (head phpinspect-name))
-                                           &optional origin-type)
+                                                               (name (head 
phpinspect-name))
+                                                               &optional 
origin-type)
   (phpi--typedef-edit def
     (phpi-pcol-delete-for-type
      (phpi-typedef-properties def) (phpi-typedef-name def) name)
@@ -486,7 +491,6 @@ them, which are then incorporated into DEF's properties."
   (phpi-typedef-delete-property
    def (phpinspect-intern-name (phpi-var-name prop))))
 
-
 (cl-defmethod phpi-typedef-delete-property-token-definition
   ((def phpinspect-typedef) (prop-name string) token-meta)
   (phpi-typedef-delete-property-token-definition
@@ -500,7 +504,6 @@ them, which are then incorporated into DEF's properties."
     (unless (phpi-prop-definition-tokens prop)
       (phpi-typedef-delete-property def prop))))
 
-
 (cl-defmethod phpi-typedef-get-properties ((def phpinspect-typedef))
   (seq-filter #'phpi-prop-vanilla-p
              (phpi-pcol-list-active (phpi-typedef-properties def))))
diff --git a/test/test-buffer-indexation.el b/test/test-buffer-indexation.el
index ef06db4fee..fab1a72d12 100644
--- a/test/test-buffer-indexation.el
+++ b/test/test-buffer-indexation.el
@@ -133,6 +133,71 @@ class A
         (should (phpinspect--type= (phpinspect--make-type :name "\\App\\Harry")
                                    (phpi-prop-type property)))))))
 
+(ert-deftest phpinspect-sync-property-type ()
+  (with-temp-buffer
+    (let ((buffer (phpinspect-claim-buffer
+                   (current-buffer) 
(phpinspect--make-dummy-composer-project-with-code))))
+
+      (insert "<?php
+
+namespace Example;
+
+use DateTime;
+
+class Sample
+{
+    private DateTime $date;
+
+    public function setDate(DateTime $date)
+    {
+        $this->date = $date;
+    }
+
+    public function getDate(): DateTime
+    {
+        return $this->date;
+    }
+}")
+      (phpinspect-buffer-update-project-index buffer)
+
+      (let ((typedef (phpinspect-project-get-typedef
+                      (phpinspect-buffer-project buffer)
+                      (phpinspect--make-type :name "\\Example\\Sample")))
+            property)
+
+        (should typedef)
+        (should (setq property (phpi-typedef-get-property typedef "date")))
+
+        (should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
+                                   (phpi-prop-type property)))
+
+        ;; Change the type of the property to DateTimeImmutable
+        (goto-char 70) ;; Position right after "private "
+        (delete-region 70 78) ;; Delete "DateTime"
+        (insert "\\DateTimeImmutable")
+        (phpinspect-buffer-update-project-index buffer)
+
+        (should (setq property (phpi-typedef-get-property typedef "date")))
+        (should (phpi-prop-type property))
+        (should (phpinspect--type= (phpinspect--make-type :name 
"\\DateTimeImmutable")
+                                   (phpi-prop-type property)))
+
+        ;; Change the type of the property to string
+        (goto-char 71) ;; Position right after "private "
+        (delete-region 71 89) ;; Delete "DateTimeImmutable"
+        (insert "string")
+        (phpinspect-buffer-update-project-index buffer)
+
+        (should (setq property (phpi-typedef-get-property typedef "date")))
+        (should (phpinspect--type= (phpinspect--make-type :name "\\string")
+                                   (phpi-prop-type property)))
+
+        ;; Remove the property
+        (goto-char 63) ;; Position right before "private string $date;"
+        (delete-region 63 82) ;; Delete "private string $date;"
+        (phpinspect-buffer-update-project-index buffer)
+
+        (should-not (phpi-typedef-get-property typedef "date"))))))
 
 (provide 'test-buffer-indexation)
 ;;; test-buffer-indexation.el ends here
diff --git a/test/test-buffer-parsing.el b/test/test-buffer-parsing.el
index ef20b62c34..483f0e17c6 100644
--- a/test/test-buffer-parsing.el
+++ b/test/test-buffer-parsing.el
@@ -62,3 +62,54 @@
       (insert "$d")
       (insert ")")
       (should (equal (phpinspect-parse-string (buffer-string)) 
(phpinspect-buffer-parse buffer))))))
+
+(ert-deftest phpinspect-parse-nested-anonymous-functions-incrementally ()
+  (with-temp-buffer
+    (let ((buffer (phpinspect-claim-buffer (current-buffer) 
(phpinspect--make-dummy-project))))
+      (insert "<?php
+class ComplexParsing {
+    private $callback;
+
+    public function __construct() {
+        $this->callback = function($x) {
+            return function($y) use ($x) {
+                return $x + $y;
+            };
+        };
+    }
+
+    public function executeCallback($value) {
+        $inner = $this->callback($value);
+        return $inner(10);
+    }
+}")
+      (should (equal (phpinspect-parse-string (buffer-string)) 
(phpinspect-buffer-parse buffer)))
+
+      ;; Insert a new method with nested anonymous functions
+      (goto-char 175)
+      (insert "\n\n    public function nestedAnonymous() {\n")
+      (insert "        $nested = function($a) {\n")
+      (insert "            return function($b) use ($a) {\n")
+      (insert "                return function($c) use ($a, $b) {\n")
+      (insert "                    return $a + $b + $c;\n")
+      (insert "                };\n")
+      (insert "            };\n")
+      (insert "        };\n")
+      (insert "        return $nested(1)(2)(3);\n")
+      (insert "    }\n")
+
+      (should (equal (phpinspect-parse-string (buffer-string)) 
(phpinspect-buffer-parse buffer)))
+
+      ;; Modify the existing method to add another level of nesting
+      (goto-char 130)
+      (insert "            return function($z) use ($x, $y) {\n")
+      (insert "                return $x + $y + $z;\n")
+      (insert "            };\n")
+
+      (should (equal (phpinspect-parse-string (buffer-string)) 
(phpinspect-buffer-parse buffer)))
+
+      ;; Add a comment inside the nested anonymous function
+      (goto-char 200)
+      (insert " // This is a comment inside a nested anonymous function\n")
+
+      (should (equal (phpinspect-parse-string (buffer-string)) 
(phpinspect-buffer-parse buffer))))))

Reply via email to