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

    Implement psr-0 and psr-4 autoloaders
---
 phpinspect-autoload.el | 109 +++++++++++++++++++++++++++++++++++++++++--------
 phpinspect-fs.el       |  46 +++++++++++++++++++--
 phpinspect.el          |   8 ++++
 test/test-autoload.el  |  91 +++++++++++++++++++++++++++++------------
 test/test-fs.el        |  13 +++---
 5 files changed, 216 insertions(+), 51 deletions(-)

diff --git a/phpinspect-autoload.el b/phpinspect-autoload.el
index 98e83d3ae8..51efa36f95 100644
--- a/phpinspect-autoload.el
+++ b/phpinspect-autoload.el
@@ -27,21 +27,6 @@
 (require 'phpinspect-project)
 (require 'phpinspect-fs)
 
-(cl-defstruct (phpinspect-autoloader
-               (:constructor phpinspect-make-autoloader))
-  (project nil
-           :type phpinspect--project
-           :documentation "The project that this autoloader can find files 
for")
-  (own-types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
-             :type hash-table
-             :documentation "The internal types that can be
-             autoloaded through this autoloader")
-  (types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
-         :type hash-table
-         :documentation
-         "The external types that can be autoloaded through this autoloader."))
-
-
 (cl-defstruct (phpinspect-psr0
                (:constructor phpinspect-make-psr0-generated))
   (prefix nil
@@ -69,7 +54,99 @@
              :documentation
              "The directories that this autoloader finds code in."))
 
-(cl-defgeneric phpinspect-al-strategy-fill-typehash (strategy typehash)
+(cl-defstruct (phpinspect-autoloader
+               (:constructor phpinspect-make-autoloader))
+  (project nil
+           :type phpinspect--project
+           :documentation "The project that this autoloader can find files 
for")
+  (own-types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
+             :type hash-table
+             :documentation "The internal types that can be
+             autoloaded through this autoloader")
+  (types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
+         :type hash-table
+         :documentation
+         "The external types that can be autoloaded through this autoloader."))
+
+(defun phpinspect-make-autoload-definition-closure (project-root fs typehash)
+  "Create a closure that can be used to `maphash' the autoload section of a 
composer-json."
+  (lambda (type prefixes)
+    (let ((strategy))
+      (cond
+       ((string= "psr-0" type)
+        (maphash
+         (lambda (prefix directory-paths)
+           (when (stringp directory-paths) (setq directory-paths (list 
directory-paths)))
+           (setq strategy (phpinspect-make-psr0-generated :prefix prefix))
+           (dolist (path directory-paths)
+             (push (concat project-root "/" path)
+                   (phpinspect-psr0-directories strategy))))
+         prefixes))
+       ((string= "psr-4" type)
+        (maphash
+         (lambda (prefix directory-paths)
+           (when (stringp directory-paths) (setq directory-paths (list 
directory-paths)))
+           (setq strategy (phpinspect-make-psr4-generated :prefix prefix))
+             (dolist (path directory-paths)
+               (push (concat project-root "/" path)
+                     (phpinspect-psr4-directories strategy))))
+         prefixes))
+       (t (phpinspect--log "Unsupported autoload strategy \"%s\" encountered" 
type)))
+
+      (when strategy
+        (phpinspect-al-strategy-fill-typehash strategy fs typehash)))))
+
+(cl-defmethod phpinspect--read-json-file (fs file)
+  (with-temp-buffer
+    (phpinspect-fs-insert-file-contents fs file)
+    (goto-char 0)
+    (phpinspect-json-preset (json-read))))
+
+(cl-defmethod phpinspect-autoloader-refresh ((autoloader 
phpinspect-autoloader))
+  "Refresh autoload definitions by reading composer.json files
+  from the project and vendor folders."
+  (let* ((project-root (phpinspect--project-root 
(phpinspect-autoloader-project autoloader)))
+         (fs (phpinspect--project-fs (phpinspect-autoloader-project 
autoloader)))
+         (vendor-dir (concat project-root "/vendor"))
+         (composer-json (phpinspect--read-json-file
+                         fs
+                         (concat project-root "/composer.json")))
+         (project-autoload (gethash "autoload" composer-json))
+         (own-types (make-hash-table :test 'eq :size 10000 :rehash-size 10000))
+         (types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)))
+
+    (when project-autoload
+      (maphash (phpinspect-make-autoload-definition-closure project-root fs 
own-types)
+               project-autoload)
+
+      (maphash (phpinspect-make-autoload-definition-closure project-root fs 
types)
+               project-autoload))
+
+    (when (phpinspect-fs-file-directory-p fs vendor-dir)
+      (dolist (author-dir (phpinspect-fs-directory-files fs vendor-dir))
+        (when (phpinspect-fs-file-directory-p fs author-dir)
+          (dolist (dependency-dir (phpinspect-fs-directory-files fs 
author-dir))
+            (when (and (phpinspect-fs-file-directory-p fs dependency-dir)
+                       (phpinspect-fs-file-exists-p fs (concat dependency-dir 
"/composer.json")))
+              (let* ((dependency-json (phpinspect--read-json-file
+                                       fs
+                                       (concat dependency-dir 
"/composer.json")))
+                     (dependency-autoload (gethash "autoload" 
dependency-json)))
+                (when dependency-autoload
+                  (maphash (phpinspect-make-autoload-definition-closure
+                            dependency-dir fs types)
+                           dependency-autoload))))))))
+
+      (setf (phpinspect-autoloader-own-types autoloader) own-types)
+      (setf (phpinspect-autoloader-types autoloader) types)))
+
+(cl-defmethod phpinspect-autoloader-resolve ((autoloader phpinspect-autoloader)
+                                            typename-symbol)
+  (or (gethash typename-symbol (phpinspect-autoloader-own-types autoloader))
+      (gethash typename-symbol (phpinspect-autoloader-types autoloader))))
+
+
+(cl-defgeneric phpinspect-al-strategy-fill-typehash (strategy fs typehash)
   "Make STRATEGY return a map with type names as keys and the
   paths to the files they are defined in as values.")
 
diff --git a/phpinspect-fs.el b/phpinspect-fs.el
index e84b155ba8..2178f78d57 100644
--- a/phpinspect-fs.el
+++ b/phpinspect-fs.el
@@ -26,12 +26,27 @@
 (cl-defstruct (phpinspect-fs (:constructor phpinspect-make-fs)))
 
 (cl-defstruct (phpinspect-virtual-fs (:constructor phpinspect-make-virtual-fs))
+  "A rough in-memory filesystem. Useful for testing."
   (files (make-hash-table :test 'equal)
                 :type hash-table
                 :documentation
                 "The files in the virtual filesystem"))
 
+(defsubst phpinspect-make-virtual-file (contents)
+  (list contents (current-time)))
+
+(defalias 'phpinspect-virtual-file-modification-time #'cadr)
+(defalias 'phpinspect-virtual-file-contents #'car)
+
+(cl-defmethod phpinspect-virtual-fs-set-file ((fs phpinspect-virtual-fs)
+                                              path
+                                              contents)
+  (puthash path (phpinspect-make-virtual-file contents)
+           (phpinspect-virtual-fs-files fs)))
+
 (cl-defgeneric phpinspect-fs-file-exists-p (fs file))
+(cl-defgeneric phpinspect-fs-file-directory-p (fs file))
+(cl-defgeneric phpinspect-fs-file-modification-time (fs file))
 (cl-defgeneric phpinspect-fs-insert-file-contents (fs file))
 (cl-defgeneric phpinspect-fs-directory-files (fs directory match))
 (cl-defgeneric phpinspect-fs-directory-files-recursively (fs directory match))
@@ -42,11 +57,36 @@
 (cl-defmethod phpinspect-fs-file-exists-p ((fs phpinspect-virtual-fs) file)
   (and (gethash file (phpinspect-virtual-fs-files fs)) t))
 
+(cl-defmethod phpinspect-fs-file-directory-p ((fs phpinspect-fs) file)
+  (file-directory-p file))
+
+(cl-defmethod phpinspect-fs-file-directory-p ((fs phpinspect-virtual-fs) file)
+  (setq file (concat (string-remove-suffix "/" file) "/"))
+  (let ((is-directory? nil))
+    (maphash
+     (lambda (existing-file _ignored)
+       (when (string-prefix-p (file-name-directory existing-file)
+                              file)
+         (setq is-directory? t)))
+     (phpinspect-virtual-fs-files fs))
+    is-directory?))
+
+(cl-defmethod phpinspect-fs-file-modification-time ((fs phpinspect-virtual-fs) 
file)
+  (let ((file (gethash file (phpinspect-virtual-fs-files fs))))
+    (when file
+      (phpinspect-virtual-file-modification-time file))))
+
+(cl-defmethod phpinspect-fs-file-modification-time ((fs phpinspect-fs) file)
+  (let ((attributes (file-attributes file)))
+    (when attributes
+      (file-attribute-modification-time attributes))))
+
 (cl-defmethod phpinspect-fs-insert-file-contents ((fs phpinspect-fs) file)
   (insert-file-contents-literally file))
 
 (cl-defmethod phpinspect-fs-insert-file-contents ((fs phpinspect-virtual-fs) 
file)
-  (insert (or (gethash file (phpinspect-virtual-fs-files fs)) "")))
+  (let ((file-obj (gethash file (phpinspect-virtual-fs-files fs))))
+    (when file (insert (or (phpinspect-virtual-file-contents file-obj) "")))))
 
 (cl-defmethod phpinspect-fs-directory-files ((fs phpinspect-fs) directory 
&optional match)
   (directory-files directory t match t))
@@ -56,11 +96,11 @@
   (let ((files))
     (maphash
      (lambda (file _ignored)
-       (let ((basename (string-remove-prefix directory file)))
+       (let ((basename (replace-regexp-in-string "/.*$" "" 
(string-remove-prefix directory file))))
          (when (and (string-prefix-p directory file)
                     (string-match-p "^[^/]*$" basename)
                     (if match (string-match-p match basename) t))
-           (push file files))))
+           (cl-pushnew (concat directory basename) files :test #'string=))))
      (phpinspect-virtual-fs-files fs))
     files))
 
diff --git a/phpinspect.el b/phpinspect.el
index ce4e323dac..0e39cb0cda 100644
--- a/phpinspect.el
+++ b/phpinspect.el
@@ -1138,6 +1138,14 @@ level of START-FILE in stead of `default-directory`."
     (set (make-local-variable 'phpinspect--buffer-project) (funcall 
phpinspect-project-root-function)))
   phpinspect--buffer-project)
 
+
+(defmacro phpinspect-json-preset (&rest body)
+  "Default options to wrap around `json-read' and similar BODY."
+  `(let ((json-object-type 'hash-table)
+            (json-array-type 'list)
+            (json-key-type 'string))
+     ,@body))
+
 ;; Use statements
 ;;;###autoload
 (defun phpinspect-fix-uses-interactive ()
diff --git a/test/test-autoload.el b/test/test-autoload.el
index 85df039d13..43417c3963 100644
--- a/test/test-autoload.el
+++ b/test/test-autoload.el
@@ -1,4 +1,4 @@
-;;; test-autoload.el --- Unit tests for phpinspect.el  -*- lexical-binding: t; 
-*-
+;; test-autoload.el --- Unit tests for phpinspect.el  -*- lexical-binding: t; 
-*-
 
 ;; Copyright (C) 2021 Free Software Foundation, Inc.
 
@@ -34,20 +34,17 @@
          (autoload
            (phpinspect-make-psr0-generated :prefix "App\\")))
 
-    (puthash "/home/user/projects/app/src/App/Services/SuperService.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/src/App/Services/SuperService.php" "")
 
-    (puthash "/home/user/projects/app/src/Kernel.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
-    (puthash "/home/user/projects/app/src/App/Controller/Banana.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/src/Kernel.php" "")
 
-    (puthash "/home/user/projects/app/lib/Mailer_Lib.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/src/App/Controller/Banana.php" "")
+
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/lib/Mailer_Lib.php" "")
 
     (setf (phpinspect-psr0-directories autoload) (list 
"/home/user/projects/app/src/"
                                                        
"/home/user/projects/app/lib/"))
@@ -76,21 +73,17 @@
          (autoload
            (phpinspect-make-psr4-generated :prefix "App\\")))
 
-    (puthash "/home/user/projects/app/src/Services/SuperService.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/src/Services/SuperService.php" "")
 
-    (puthash "/home/user/projects/app/src/Kernel.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/src/Kernel.php" "")
 
-    (puthash "/home/user/projects/app/src/Controller/Banana.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/src/Controller/Banana.php" "")
 
-    (puthash "/home/user/projects/app/lib/Mailer_Lib.php"
-             ""
-             (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file
+     fs "/home/user/projects/app/lib/Mailer_Lib.php" "")
 
     (setf (phpinspect-psr4-directories autoload) (list 
"/home/user/projects/app/src/"
                                                        
"/home/user/projects/app/lib/"))
@@ -112,3 +105,51 @@
     (should (string= "/home/user/projects/app/lib/Mailer_Lib.php"
                      (gethash (phpinspect-intern-name "\\App\\Mailer_Lib")
                               typehash)))))
+
+(ert-deftest phpinspect-autoloader-refresh ()
+  (let* ((fs (phpinspect-make-virtual-fs))
+         (project (phpinspect--make-project
+                   :fs fs
+                   :root "/project/root"))
+         (autoloader (phpinspect-make-autoloader
+                      :project project)))
+    (phpinspect-virtual-fs-set-file
+     fs
+     "/project/root/composer.json"
+     "{ \"autoload\": { \"psr-4\": {\"App\\\\Banana\\\\\": [\"src/\", 
\"lib\"]}}}")
+
+    (phpinspect-virtual-fs-set-file fs "/project/root/src/TestClass.php" "")
+
+    (phpinspect-virtual-fs-set-file
+     fs
+    "/project/root/vendor/runescape/client/composer.json"
+    "{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", 
\"lib\"]}}}")
+
+    (phpinspect-virtual-fs-set-file
+     fs "/project/root/vendor/runescape/client/src/TestClass.php" "")
+
+     (phpinspect-virtual-fs-set-file
+      fs
+      "/project/root/vendor/runescape/client/src/Runescape/Banana/App.php"
+      "")
+
+     (phpinspect-virtual-fs-set-file
+      fs "/project/root/vendor/runescape/client/src/LibClass.php" "")
+
+     (phpinspect-virtual-fs-set-file
+      fs
+      "/project/root/vendor/not-runescape/wow/composer.json"
+      "{ \"autoload\": { \"psr-4\": {\"WoW\\\\Dwarves\\\\\": \"src/\"}}}")
+
+     (phpinspect-virtual-fs-set-file
+      fs "/project/root/vendor/not-runescape/wow/src/TestClass.php" "")
+
+    (phpinspect-autoloader-refresh autoloader)
+
+    (should-not (hash-table-empty-p (phpinspect-autoloader-own-types 
autoloader)))
+    (should-not (hash-table-empty-p (phpinspect-autoloader-types autoloader)))
+
+    (should (string= 
"/project/root/vendor/runescape/client/src/Runescape/Banana/App.php"
+                     (phpinspect-autoloader-resolve
+                      autoloader
+                      (phpinspect-intern-name "\\Runescape\\Banana\\App"))))))
diff --git a/test/test-fs.el b/test/test-fs.el
index c83ebfe24b..c15f447fa3 100644
--- a/test/test-fs.el
+++ b/test/test-fs.el
@@ -27,13 +27,13 @@
 
 (ert-deftest phpinspect-virtual-fs-file-exists-p ()
   (let ((fs (phpinspect-make-virtual-fs)))
-    (puthash "/test/test.txt" "contents" (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file fs "/test/test.txt" "contents")
 
     (should (phpinspect-fs-file-exists-p fs "/test/test.txt"))))
 
 (ert-deftest phpinspect-virtual-fs-insert-file-contents ()
   (let ((fs (phpinspect-make-virtual-fs)))
-    (puthash "/test/test.txt" "contents" (phpinspect-virtual-fs-files fs))
+    (phpinspect-virtual-fs-set-file fs "/test/test.txt" "contents")
 
     (with-temp-buffer
       (phpinspect-fs-insert-file-contents fs "/test/test.txt")
@@ -67,11 +67,10 @@
       (should (member "/a/b/c/aaa.match" files))
       (should (not (member "/a/b/c/nope.nomatch" files))))
 
-    (let ((files (phpinspect-fs-directory-files-recursively fs "/a/b" 
"\\.match$")))
-      (should (member "/a/b/c/dee.match" files))
-      (should (member "/a/b/c/cee.match" files))
-      (should (member "/a/b/c/aaa.match" files))
-      (should (not (member "/a/b/c/nope.nomatch" files))))
+    (let ((files (phpinspect-fs-directory-files fs "/a/b")))
+      (should (member "/a/b/c" files))
+      (should (member "/a/b/d" files))
+      (should (= 2 (length files))))
 
     (let ((files (phpinspect-fs-directory-files-recursively fs "/a/b")))
       (should (member "/a/b/c/dee.match" files))

Reply via email to