emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/phpinspect 328de9a658 2/2: Implement "classmap" autoloa


From: ELPA Syncer
Subject: [elpa] externals/phpinspect 328de9a658 2/2: Implement "classmap" autoload directive
Date: Sat, 28 Sep 2024 09:58:38 -0400 (EDT)

branch: externals/phpinspect
commit 328de9a658d21efd1bc1f959f20eaefb0c93ecb0
Author: Hugo Thunnissen <devel@hugot.nl>
Commit: Hugo Thunnissen <devel@hugot.nl>

    Implement "classmap" autoload directive
    
    Performance is not ideal, especially when working in several projects at
    once. There are solid possibilities for optimization but this will do for an
    initial implementation.
---
 phpinspect-autoload.el | 184 +++++++++++++++++++++++++++++++++++++++++--------
 phpinspect-cache.el    |   2 +-
 phpinspect-project.el  |  23 ++++---
 phpinspect.el          |  44 ++++++++++--
 4 files changed, 207 insertions(+), 46 deletions(-)

diff --git a/phpinspect-autoload.el b/phpinspect-autoload.el
index b559ffa532..866db90838 100644
--- a/phpinspect-autoload.el
+++ b/phpinspect-autoload.el
@@ -28,8 +28,33 @@
 (require 'phpinspect-util)
 (require 'phpinspect-type)
 (require 'phpinspect-pipeline)
+(require 'phpinspect-typedef)
 (require 'json)
 
+(defcustom phpinspect-autoload-classmaps nil
+  "Enable support for classmap auoload directives.
+
+The classmap autoload directive is a key in the composer.json
+which allows package authors to define directories which should
+be parsed wholesale to discover available classes and functions.
+
+Enabling this feature can make indexation take considerably
+longer. It can also make the in-memory index larger as all files
+in the classmap directories are indexed in their entirety.
+
+On low powered systems, you may want to keep this feature
+disabled unless your projects depend heavily on code which uses
+the classmap directive.
+
+Note: A prominent package which uses the classmap directive is
+      PHPUnit. Users of PHPUnit will want to have this feature
+      enabled.
+
+As of [2024-09-28] this is a new feature and therefore subject to
+change and not enabled by default."
+  :type 'boolean
+  :group 'phpinspect)
+
 (cl-defstruct (phpinspect-psrX
                (:constructor phpinspect-make-psrX-generated))
   "A base structure to be included in PSR autoload strategy
@@ -75,7 +100,12 @@ execution of this strategy."))
   (list nil
         :type list
         :documentation
-        "List of files to be indexed"))
+        "List of files to be indexed")
+  (own nil
+       :type boolean))
+
+(cl-defstruct (phpinspect-classmap (:constructor phpinspect-make-classmap)
+                                   (:include phpinspect-files)))
 
 (cl-defgeneric phpinspect-al-strategy-request-type-name (_strategy _file-name)
   "Returns FQN when STRATEGY is responsible for autoloading FILE-NAME.
@@ -114,6 +144,10 @@ qualified name of a type should be based on the file name 
alone."
 
 (cl-defstruct (phpinspect-autoloader
                (:constructor phpinspect-make-autoloader))
+  (-progress-reporter nil
+                     :type progress-reporter)
+  (-type-counter 0
+                 :type integer)
   (refresh-thread nil
                   :type thread)
   (fs nil
@@ -154,19 +188,37 @@ could be added as imports for an ambiguous bare type 
name.")
    :documentation
    "List of autoload strategies local to the project."))
 
-;; FIXME: This is another scenario where an LRU Cache might come in handy (we
-;; don't want to re-compare string prefixes everytime the same namespace is
-;; checked).
-(defun phpinspect-autoloader-get-own-types-in-namespace (al namespace)
+(defun phpinspect-autoloader-get-types-in-namespace (al namespace &optional 
exclude-vendor)
+  "Find types known to AL, defined in NAMESPACE.
+
+If EXCLUDE-VENDOR is nil, all known namespaces are queried. If it
+is non-nil, only project-local namespaces are queried.
+
+NAMESPACE must be a string. It will be resolved using
+`phpinspect--resolve-type-name' to ensure that it is a FQN.
+
+Return value is a list containing instances of
+`phpinspect--type'."
   (cl-assert (stringp namespace))
-  (let ((namespace-fqn (phpinspect--resolve-type-name nil nil namespace))
-       types)
-    (dolist (name (hash-table-keys (phpinspect-autoloader-own-types al)))
-      (when (string-prefix-p namespace-fqn (phpinspect-name-string name))
-       (push (phpinspect--make-type-generated :name name :fully-qualified t)
-             types)))
+
+  (let ((namespace-fqn (phpinspect-intern-name
+                        (phpinspect--resolve-type-name nil nil namespace)))
+        types)
+
+    (maphash (lambda (key _ignored)
+               (when (eq namespace-fqn (phpinspect-name-namespace key))
+                 (push (phpinspect--make-type-generated :name key 
:fully-qualified t) types)))
+
+             (if exclude-vendor
+                 (phpinspect-autoloader-own-types al)
+               (phpinspect-autoloader-types al)))
+
     types))
 
+(defun phpinspect-autoloader-get-own-types-in-namespace (al namespace)
+  "Find types known to AL, defined in project-local NAMESPACE."
+  (phpinspect-autoloader-get-types-in-namespace al namespace 'exclude-vendor))
+
 (cl-defmethod phpinspect--read-json-file (fs file)
   (with-temp-buffer
     (phpinspect-fs-insert-file-contents fs file)
@@ -264,19 +316,70 @@ re-executes the strategy."
     (when own
       (push strat (phpinspect-autoloader-local-strategies al)))))
 
+(cl-defmethod phpinspect-files-compute-list ((strat phpinspect-files))
+  (phpinspect-files-list strat))
+
+(defun phpinspect-php-file-extension-p (file-name)
+  (equal (file-name-extension file-name)  "php"))
+
+(defun phpinspect--file-expand-wildcards (file-name)
+  "Like `file-expand-wildcards', but returns single element list if
+FILE-NAME does not contain any wildcards, instead of nil."
+  (or (file-expand-wildcards file-name t) (list file-name)))
+
+(cl-defmethod phpinspect-files-compute-list ((strat phpinspect-classmap))
+  "Generate file list for classmap directories."
+  (let* ((fs (phpinspect-autoloader-fs (phpinspect-files-autoloader strat)))
+         (directories
+          (thread-last (phpinspect-files-list strat)
+                       ;; Handle wildcards
+                       ;; (https://getcomposer.org/doc/04-schema.md#classmap)
+                       (mapcar #'phpinspect--file-expand-wildcards)
+                       (apply #'append)))
+         files)
+    (dolist (dir directories)
+      (pcase dir
+        ((pred (phpinspect-fs-file-directory-p fs))
+         (setq files
+               (nconc files (phpinspect-fs-directory-files-recursively fs dir 
"\\.php$"))))
+        ((pred phpinspect-php-file-extension-p)
+         (push dir files))
+        (_ (phpinspect-message
+            "Unexpected file in classmap directive: %s (not a directory nor a 
PHP file)" dir))))
+    files))
+
 (cl-defmethod phpinspect-al-strategy-execute ((strat phpinspect-files))
-  (phpinspect--log "indexing files list: %s" (phpinspect-files-list strat))
-  (let* ((indexer (phpinspect-autoloader-file-indexer 
(phpinspect-files-autoloader strat)))
+  (let* ((list (phpinspect-files-compute-list strat))
+         (al (phpinspect-files-autoloader strat))
+         (types (phpinspect-autoloader-types al))
+         (own-types (phpinspect-autoloader-own-types al))
+         (own (phpinspect-files-own strat))
+         (indexer (phpinspect-autoloader-file-indexer al))
          (wrapped-indexer (lambda (file)
                             (condition-case-unless-debug err
-                                (funcall indexer file)
+                                (let ((result (funcall indexer file)))
+                                  (dolist (index result)
+                                    (when (phpinspect-typedef-p index)
+                                      ;; Make autoloader aware of indexed 
types.
+                                      (let ((name (phpinspect--type-name 
(phpi-typedef-name index))))
+                                      (puthash name file types)
+                                      (when own
+                                        (puthash name file own-types))
+
+                                      (phpinspect-autoloader-put-type-bag al 
name)))))
                               (t (phpinspect--log "Error indexing file %s: %s" 
file err))))))
-    (phpinspect-pipeline (phpinspect-files-list strat)
+    (phpinspect--log "indexing files list: %s" list)
+    (phpinspect-pipeline list
       :into (funcall :with-context wrapped-indexer))))
 
 (cl-defmethod phpinspect-autoloader-put-type-bag ((al phpinspect-autoloader) 
(type-fqn (head phpinspect-name)))
   (let* ((base-name (phpinspect-name-base type-fqn))
          (bag (gethash base-name (phpinspect-autoloader-type-name-fqn-bags 
al))))
+    (when-let ((pr (phpinspect-autoloader--progress-reporter al))
+               (i (cl-incf (phpinspect-autoloader--type-counter al)))
+               ((= 0 (mod i 10))))
+      (progress-reporter-update pr i (format "%d types found" i)))
+
     (if bag
         (setcdr bag (cons type-fqn (cdr bag)))
       (push type-fqn bag)
@@ -336,13 +439,22 @@ re-executes the strategy."
                      (push strategy batch))
                    prefixes))
                  ("files"
-                  (setq strategy
-                        (phpinspect-make-files
+                  (push (phpinspect-make-files
                          :list (mapcar
                                 (lambda (file) (file-name-concat project-root 
file))
                                 prefixes)
-                         :autoloader al))
-                  (push strategy batch))
+                         :autoloader al
+                         :own (eq 'local (car file)))
+                        batch))
+                 ("classmap"
+                  (when phpinspect-autoload-classmaps
+                    (push (phpinspect-make-classmap
+                           :list (mapcar
+                                  (lambda (dir) (file-name-concat project-root 
dir))
+                                  prefixes)
+                           :autoloader al
+                           :own (eq 'local (car file)))
+                          batch)))
                  (_ (phpinspect--log "Unsupported autoload strategy \"%s\" 
encountered" type)))))
            autoload)))
       (phpinspect--log "Number of autoload strategies in batch: %s" (length 
batch))
@@ -365,7 +477,7 @@ re-executes the strategy."
   (or (gethash typename (phpinspect-autoloader-own-types autoloader))
       (gethash typename (phpinspect-autoloader-types autoloader))))
 
-(cl-defmethod phpinspect-autoloader-refresh ((autoloader 
phpinspect-autoloader) &optional async-callback)
+(cl-defmethod phpinspect-autoloader-refresh ((autoloader 
phpinspect-autoloader) &optional async-callback report-progress)
   "Refresh autoload definitions by reading composer.json files
   from the project and vendor folders."
   (let* ((project-root (funcall (phpinspect-autoloader-project-root-resolver 
autoloader)))
@@ -377,19 +489,31 @@ re-executes the strategy."
     (setf (phpinspect-autoloader-types autoloader)
           (make-hash-table :test 'eq :size 10000 :rehash-size 10000))
 
+    (when report-progress
+      (message "Setting progress reporter")
+      (setf (phpinspect-autoloader--progress-reporter autoloader)
+            (make-progress-reporter
+                     (format "[phpinspect] indexing %s" (file-name-base 
project-root)))))
+
     (let ((time-start (current-time)))
       (setf (phpinspect-autoloader-refresh-thread autoloader)
             (phpinspect-pipeline (phpinspect-find-composer-json-files fs 
project-root)
-              :async (or async-callback
-                         (lambda (_result error)
-                           (if error
-                               (phpinspect-message "Error during autoloader 
refresh: %s" error)
-                             (phpinspect-message
-                              (concat "Refreshed project autoloader. Found %d 
types within project,"
-                                      " %d types total. (finished in %d ms)")
-                              (hash-table-count 
(phpinspect-autoloader-own-types autoloader))
-                              (hash-table-count (phpinspect-autoloader-types 
autoloader))
-                              (string-to-number (format-time-string "%s%3N" 
(time-since time-start)))))))
+              :async (lambda (result error)
+                       (when report-progress
+                         (progress-reporter-done 
(phpinspect-autoloader--progress-reporter autoloader))
+                         (setf (phpinspect-autoloader--progress-reporter 
autoloader) nil))
+
+                       (funcall (or async-callback
+                                    (lambda (_result error)
+                                      (if error
+                                          (phpinspect-message "Error during 
autoloader refresh: %s" error)
+                                        (phpinspect-message
+                                         (concat "Refreshed project 
autoloader. Found %d types within project,"
+                                                 " %d types total. (finished 
in %d ms)")
+                                         (hash-table-count 
(phpinspect-autoloader-own-types autoloader))
+                                         (hash-table-count 
(phpinspect-autoloader-types autoloader))
+                                         (string-to-number (format-time-string 
"%s%3N" (time-since time-start)))))))
+                                result error))
               :into (phpinspect-iterate-composer-jsons :with-context 
autoloader)
               :into phpinspect-al-strategy-execute)))))
 
diff --git a/phpinspect-cache.el b/phpinspect-cache.el
index aa8dac8e5f..f059bfaaeb 100644
--- a/phpinspect-cache.el
+++ b/phpinspect-cache.el
@@ -185,7 +185,7 @@ then returned."
                            :file-indexer (phpinspect-project-make-file-indexer 
project)
                            :project-root-resolver 
(phpinspect-project-make-root-resolver project))))
           (setf (phpinspect-project-autoload project) autoloader)
-          (phpinspect-autoloader-refresh autoloader)
+          (phpinspect-autoloader-refresh autoloader nil 'report-progress)
           (phpinspect-project-enqueue-include-dirs project))))
     project))
 
diff --git a/phpinspect-project.el b/phpinspect-project.el
index 2b5003981b..d0f663316a 100644
--- a/phpinspect-project.el
+++ b/phpinspect-project.el
@@ -108,14 +108,19 @@ serious performance hits. Enable at your own risk (:")
 (cl-defmethod phpinspect-project-add-index
   ((project phpinspect-project) (index (head phpinspect--root-index)) 
&optional index-dependencies)
   (phpinspect-project-edit project
-    (when index-dependencies
-      (phpinspect-project-enqueue-imports project (alist-get 'imports (cdr 
index))))
+    (let (indexed)
+      (when index-dependencies
+        (phpinspect-project-enqueue-imports project (alist-get 'imports (cdr 
index))))
 
-    (dolist (indexed-typedef (alist-get 'classes (cdr index)))
-      (phpinspect-project-add-typedef project (cdr indexed-typedef) 
index-dependencies))
+      (dolist (indexed-typedef (alist-get 'classes (cdr index)))
+        (push (phpinspect-project-add-typedef project (cdr indexed-typedef) 
index-dependencies)
+              indexed))
 
-    (dolist (func (alist-get 'functions (cdr index)))
-      (phpinspect-project-set-function project func))))
+      (dolist (func (alist-get 'functions (cdr index)))
+        (phpinspect-project-set-function project func)
+        (push func indexed))
+
+      indexed)))
 
 (cl-defmethod phpinspect-project-add-index ((_project phpinspect-project) 
_index)
   (cl-assert (not _index))
@@ -202,7 +207,9 @@ serious performance hits. Enable at your own risk (:")
         (when index-dependencies
           (phpinspect-project-enqueue-types project 
(phpi-typedef-get-dependencies typedef)))
 
-        (puthash typedef-name typedef (phpinspect-project-typedef-index 
project))))))
+        (puthash typedef-name typedef (phpinspect-project-typedef-index 
project))
+
+        typedef))))
 
 (cl-defmethod phpinspect-project-set-typedef
   ((project phpinspect-project) (typedef-fqn phpinspect--type) (typedef 
phpinspect-typedef))
@@ -299,7 +306,7 @@ before the search is executed."
   (let* ((autoloader (phpinspect-project-autoload project)))
     (when (eq index-new 'index-new)
       (phpinspect-project-edit project
-        (phpinspect-autoloader-refresh autoloader)))
+        (phpinspect-autoloader-refresh autoloader nil 'report-progress)))
     (let* ((result (phpinspect-autoloader-resolve
                     autoloader (phpinspect--type-name type))))
       (if (not result)
diff --git a/phpinspect.el b/phpinspect.el
index 923c55d5a7..7a239faf15 100644
--- a/phpinspect.el
+++ b/phpinspect.el
@@ -219,21 +219,51 @@ To automatically add missing use statements for used 
classes to a
 visited file, use `phpinspect-fix-imports'
 (bound to \\[phpinspect-fix-imports]].)
 
-By default, phpinspect looks for a composer.json file that can be
-used to get autoload information for the classes that are present
-in your project. It is also possible to index an entire directory
-by adding it as an include dir. To do this, use
+By default, phpinspect loads code like PHP does: via standards
+compliant autoloading. Upon opening a file and activating
+phpinspect-mode, phpinspect will look for a composer.json file to
+extract autoload-information from. Supported autoload directives
+are:
+  - files: list of files to parse/index wholesale
+  - PSR-0: directory with nested subdirectories structured according to
+           the namespacing scheme.
+  - PSR-4: PSR-0 directory with namespace prefix
+  - classmap: Directories/files to parse and index wholesale.
+              (not enabled by default, see additional note)
+
+Note on classmap directive: As of [2024-09-28], the classmap
+autoload directive has been implemented but is not enabled by
+default. It can be enabled by setting
+`phpinspect-autoload-classmaps' to `t'.
+
+It is also possible to wholesale index an entire directory by
+adding it as an include dir. To do this, use
 \\[phpinspect-project-add-include-dir]. Include directories can
 be edited at all times using \\[customize-group] RET phpinspect.
+Include dirs do not depend on the project using composer.
 
 Because of limitations in the current autoloader implementation,
-you will have to run \\[phpinspect-index-current-project] every
-time you create a new autoloadable file.
+you will have to run \\[phpinspect-index-current-project] when
+you delete a file, for it to be removed from the autoloader.
 
 Example configuration if you already have a completion
 UI (Company, Corfu) setup that can take advantage of completion
 at point (capf) functions:
 
+With `use-package':
+    (use-package phpinspect
+      :ensure nil
+      :commands (phpinspect-mode)
+      :bind ((\"C-c c\" . phpinspect-find-own-class-file)
+             (\"C-c u\" . phpinspect-fix-imports)
+             :map phpinspect-mode-map
+             (\"C-c a\" . phpinspect-find-class-file))
+      ;; Automatically add missing imports before saving a file
+      :hook ((before-save . phpinspect-fix-imports))
+      :custom (phpinspect-autoload-classmaps t
+               \"Enable classmap autoload directive\"))
+
+With a classic hook function:
     (defun my-php-personal-hook ()
       ;; Shortcut to add use statements for classes you use.
       (define-key php-mode-map (kbd \"C-c u\") #\\='phpinspect-fix-imports)
@@ -418,7 +448,7 @@ before the search is executed."
     ;; appear frozen while the thread is executing.
     (redisplay)
 
-    (phpinspect-autoloader-refresh autoloader)))
+    (phpinspect-autoloader-refresh autoloader nil 'report-progress)))
 
 (defun phpinspect-index-current-project ()
   "Index all available FQNs in the current project."



reply via email to

[Prev in Thread] Current Thread [Next in Thread]