[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."