Implement basic support for function indexation and include dirs
ci/woodpecker/push/woodpecker Pipeline failed Details

Does not yet include support for imported namespaced functions
WIP-cache
Hugo Thunnissen 9 months ago
parent 05ca0ace20
commit 55413ea9fb

@ -68,7 +68,8 @@ then returned."
(phpinspect--cache-projects cache)))
(let ((autoloader (phpinspect-make-autoloader :project project)))
(setf (phpinspect-project-autoload project) autoloader)
(phpinspect-autoloader-refresh autoloader)))
(phpinspect-autoloader-refresh autoloader)
(phpinspect-project-enqueue-include-dirs project)))
project))
(provide 'phpinspect-cache)

@ -144,8 +144,22 @@ and CONTEXT. All strategies must implement this method.")
(rctx phpinspect--resolvecontext))
(phpinspect-suggest-attributes-at-point rctx 'static))
(cl-defstruct (phpinspect-comp-word (:constructor phpinspect-make-comp-word))
"Comletion strategy for bare words")
(cl-defmethod phpinspect-comp-strategy-supports
((_strat phpinspect-comp-word) (_q phpinspect-completion-query)
(rctx phpinspect--resolvecontext))
(phpinspect-word-p (car (last (phpinspect--resolvecontext-subject rctx)))))
(cl-defmethod phpinspect-comp-strategy-execute
((_strat phpinspect-comp-word) (_q phpinspect-completion-query)
(rctx phpinspect--resolvecontext))
(phpinspect-suggest-functions rctx))
(defvar phpinspect-completion-strategies (list (phpinspect-make-comp-attribute)
(phpinspect-make-comp-sigil)
(phpinspect-make-comp-word)
(phpinspect-make-comp-static-attribute))
"List of completion strategies that phpinspect can use.")
@ -198,4 +212,5 @@ Returns list of `phpinspect--completion'."
phpinspect--null-type)))
:kind 'variable))
(provide 'phpinspect-completion)

@ -178,7 +178,26 @@ be implemented for return values of `phpinspect-eld-strategy-execute'")
(phpinspect--class-get-static-method class method-name-sym)
(phpinspect--class-get-method class method-name-sym)))))
(when method
(phpinspect-make-function-doc :fn method :arg-pos arg-pos))))))))
(phpinspect-make-function-doc :fn method :arg-pos arg-pos))))
((setq match-result (phpinspect--match-sequence (last statement 2)
:f (phpinspect-meta-wrap-token-pred #'phpinspect-word-p)
:f (phpinspect-meta-wrap-token-pred #'phpinspect-list-p)))
(phpinspect--log "Eldoc context is a function call")
(setq arg-list (car (last match-result))
arg-pos (seq-reduce
(lambda (count meta)
(if (phpinspect-comma-p (phpinspect-meta-token meta))
(+ count 1)
count))
(phpinspect-meta-find-children-before arg-list (phpinspect-eldoc-query-point q)) 0))
(let ((func (phpinspect-project-get-function
(phpinspect--resolvecontext-project rctx)
(phpinspect-intern-name (cadr (phpinspect-meta-token (car match-result)))))))
(phpinspect--log "Got past that")
(when func
(phpinspect-make-function-doc :fn func :arg-pos arg-pos))))))))
(cl-defmethod phpinspect-eldoc-string ((var phpinspect--variable))
(concat (truncate-string-to-width

@ -59,7 +59,7 @@ of TYPE, if available."
(or (not type)
(phpinspect--type= type phpinspect--object-type)))
(defun phpinspect--index-function-from-scope (type-resolver scope comment-before &optional add-used-types)
(defun phpinspect--index-function-from-scope (type-resolver scope comment-before &optional add-used-types namespace)
"Index a function inside SCOPE token using phpdoc metadata in COMMENT-BEFORE.
If ADD-USED-TYPES is set, it must be a function and will be
@ -104,7 +104,7 @@ function (think \"new\" statements, return types etc.)."
(phpinspect--make-function
:scope `(,(car scope))
:name (cadadr (cdr declaration))
:name (concat (if namespace (concat namespace "\\") "") (cadadr (cdr declaration)))
:return-type (or type phpinspect--null-type)
:arguments (phpinspect--index-function-arg-list
type-resolver
@ -384,25 +384,54 @@ NAMESPACE will be assumed the root namespace if not provided"
(mapcar #'phpinspect--use-to-type uses))
(defun phpinspect--index-namespace (namespace type-resolver-factory location-resolver)
(phpinspect--index-classes-in-tokens
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
namespace
type-resolver-factory location-resolver (cadadr namespace)))
(let* (used-types
(index
`((classes . ,(phpinspect--index-classes-in-tokens
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
namespace
type-resolver-factory location-resolver (cadadr namespace)))
(functions . ,(phpinspect--index-functions-in-tokens
namespace
type-resolver-factory
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
(cadadr namespace)
(lambda (types) (setq used-types (nconc used-types types))))))))
(push `(used-types . ,used-types) index)))
(defun phpinspect--index-namespaces
(namespaces type-resolver-factory location-resolver &optional indexed)
(if namespaces
(progn
(push (phpinspect--index-namespace (pop namespaces)
type-resolver-factory
location-resolver)
indexed)
(phpinspect--index-namespaces namespaces type-resolver-factory
location-resolver indexed))
(apply #'append (nreverse indexed))))
(defun phpinspect--index-functions (&rest _args)
"TODO: implement function indexation. This is a stub function.")
(let ((namespace-index
(phpinspect--index-namespace
(pop namespaces) type-resolver-factory location-resolver)))
(if indexed
(progn
(nconc (alist-get 'used-types indexed)
(alist-get 'used-types namespace-index))
(nconc (alist-get 'classes indexed)
(alist-get 'classes namespace-index))
(nconc (alist-get 'functions indexed)
(alist-get 'functions namespace-index)))
(setq indexed namespace-index))
(phpinspect--index-namespaces
namespaces type-resolver-factory location-resolver indexed))
indexed))
(defun phpinspect--index-functions-in-tokens (tokens type-resolver-factory &optional imports namespace add-used-types)
"TODO: implement function indexation. This is a stub function."
(let ((type-resolver (funcall type-resolver-factory imports nil namespace))
comment-before functions)
(dolist (token tokens)
(cond ((phpinspect-comment-p token)
(setq comment-before token))
((phpinspect-function-p token)
(push (phpinspect--index-function-from-scope
type-resolver `(:public ,token) comment-before add-used-types
namespace)
functions))))
functions))
(defun phpinspect--find-used-types-in-tokens (tokens)
"Find usage of the \"new\" keyword in TOKENS.
@ -438,27 +467,35 @@ Return value is a list of the types that are \"newed\"."
(defun phpinspect--index-tokens (tokens &optional type-resolver-factory location-resolver)
"Index TOKENS as returned by `phpinspect--parse-current-buffer`."
(unless type-resolver-factory
(setq type-resolver-factory #'phpinspect--make-type-resolver))
(unless location-resolver
(setq location-resolver (lambda (_) (list 0 0))))
(let ((imports (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p tokens))))
`(phpinspect--root-index
(imports . ,imports)
,(append
(append '(classes)
(phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-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))
;; TODO: Implement function indexation
))
(or
(condition-case err
(progn
(unless type-resolver-factory
(setq type-resolver-factory #'phpinspect--make-type-resolver))
(unless location-resolver
(setq location-resolver (lambda (_) (list 0 0))))
(let* ((imports (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p tokens)))
(namespace-index
(phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-p tokens)
type-resolver-factory
location-resolver)))
`(phpinspect--root-index
(imports . ,imports)
(classes ,@(append
(alist-get 'classes namespace-index)
(phpinspect--index-classes-in-tokens
imports tokens type-resolver-factory location-resolver)))
(used-types ,@(append
(alist-get 'used-types namespace-index)
(phpinspect--find-used-types-in-tokens tokens)))
(functions . ,(append
(alist-get 'functions namespace-index)
(phpinspect--index-functions-in-tokens
tokens type-resolver-factory imports))))))
(t (phpinspect--log "phpinspect--index-tokens failed: %s" err) nil))
'(phpinspect--root-index)))
(defun phpinspect-get-or-create-cached-project-class (project-root class-fqn)
(when project-root

@ -191,6 +191,7 @@ user input.")
(setq ,continue-running nil))
(phpinspect-pipeline--enqueue ,out-queue ,outgoing)))
(phpinspect-pipeline-incoming)
(quit)
(t (phpinspect--log "Pipeline thread errored: %s" err)
(setq ,end (phpinspect-make-pipeline-end :thread (current-thread) :error err))
(setq ,continue-running nil)

@ -46,11 +46,16 @@ serious performance hits. Enable at your own risk (:")
phpinspect--buffer-project)
(cl-defstruct (phpinspect-project (:constructor phpinspect--make-project))
(class-index (make-hash-table :test 'eq :size 100 :rehash-size 40)
(class-index (make-hash-table :test 'eq :size 100 :rehash-size 1.5)
:type hash-table
:documentation
"A `hash-table` that contains all of the currently
indexed classes in the project")
(function-index (make-hash-table :test 'eq :size 100 :rehash-size 2.0)
:type hash-table
:documentation
"A hash able that contains all of the currently indexed functions
in the project")
(fs nil
:type phpinspect-fs
:documentation
@ -115,9 +120,11 @@ indexed by the absolute paths of the files they're watching."))
(not (or (phpinspect--class-initial-index class))))
(when (not class)
(setq class (phpinspect-project-create-class project type)))
(phpinspect--log "Adding unpresent class %s to index queue" type)
(phpinspect-worker-enqueue (phpinspect-project-worker project)
(phpinspect-make-index-task project type))))))
(unless (or (phpinspect--type= phpinspect--null-type type)
(phpinspect--type-is-native type))
(phpinspect--log "Adding unpresent class %s to index queue" type)
(phpinspect-worker-enqueue (phpinspect-project-worker project)
(phpinspect-make-index-task project type)))))))
(cl-defmethod phpinspect-project-add-class-attribute-types-to-index-queue
((project phpinspect-project) (class phpinspect--class))
@ -135,8 +142,29 @@ indexed by the absolute paths of the files they're watching."))
((project phpinspect-project) (index (head phpinspect--root-index)) &optional index-imports)
(when index-imports
(phpinspect-project-enqueue-imports project (alist-get 'imports (cdr index))))
(dolist (indexed-class (alist-get 'classes (cdr index)))
(phpinspect-project-add-class project (cdr indexed-class) index-imports)))
(phpinspect-project-add-class project (cdr indexed-class) index-imports))
(dolist (func (alist-get 'functions (cdr index)))
(phpinspect-project-set-function project func)))
(cl-defmethod phpinspect-project-set-function
((project phpinspect-project) (func phpinspect--function))
(puthash (phpinspect--function-name-symbol func) func
(phpinspect-project-function-index project)))
(cl-defmethod phpinspect-project-get-function
((project phpinspect-project) (name symbol))
(gethash name (phpinspect-project-function-index project)))
(cl-defmethod phpinspect-project-get-functions ((project phpinspect-project))
(let ((funcs))
(maphash
(lambda (_name func) (push func funcs))
(phpinspect-project-function-index project))
funcs))
(cl-defmethod phpinspect-project-enqueue-imports
((project phpinspect-project) imports)
@ -237,5 +265,45 @@ before the search is executed."
(cl-defmethod phpinspect-project-add-file-index ((project phpinspect-project) (filename string))
(phpinspect-project-add-index project (phpinspect-project-index-file project filename)))
(defun phpinspect-project-enqueue-include-dirs (project)
(interactive (list (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect-current-project-root))))
(let ((dirs (alist-get 'include-dirs
(alist-get (phpinspect-project-root project)
phpinspect-projects
nil nil #'string=))))
(dolist (dir dirs)
(message "enqueueing dir %s" dir)
(phpinspect-worker-enqueue
(phpinspect-project-worker project)
(phpinspect-make-index-dir-task :dir dir :project project)))))
(defgroup phpinspect '((phpinspect-projects custom-variable))
"PHPInspect Configuration")
(defcustom phpinspect-projects nil
"PHPInspect Projects."
:type '(alist :key-type string
:value-type (alist :key-type symbol
:options ((include-dirs (repeat string))))))
(defun phpinspect-project-add-include-dir (dir)
"Configure DIR as an include dir for the current project."
(interactive (list (read-directory-name "Include Directory: ")))
(custom-set-variables '(phpinspect-projects))
(let ((existing
(alist-get (phpinspect-current-project-root) phpinspect-projects nil #'string=)))
(if existing
(push dir (alist-get 'include-dirs existing))
(push `(,(phpinspect-current-project-root) . ((include-dirs . (,dir)))) phpinspect-projects)))
(customize-save-variable 'phpinspect-projects phpinspect-projects)
(phpinspect-project-enqueue-include-dirs (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect-current-project-root))))
(provide 'phpinspect-project)
;;; phpinspect-project.el ends here

@ -30,6 +30,11 @@
(require 'phpinspect-project)
(require 'phpinspect-class)
(defun phpinspect-suggest-functions (rctx)
(let* ((project (phpinspect--resolvecontext-project rctx))
(word (cadr (car (last (phpinspect--resolvecontext-subject rctx))))))
(phpinspect-project-get-functions project)))
(defun phpinspect-suggest-variables-at-point (resolvecontext)
(phpinspect--log "Suggesting variables at point")
(let ((variables))

@ -172,10 +172,10 @@ NAMESPACE may be nil, or a string with a namespace FQN."
(defun phpinspect--make-type-resolver (types &optional token-tree namespace)
"Little wrapper closure to pass around and resolve types with."
(let* ((inside-class
(if token-tree (or (phpinspect--find-innermost-incomplete-class token-tree)
(phpinspect--find-class-token token-tree))))
(inside-class-name (if inside-class (phpinspect--get-class-name-from-token
inside-class))))
(and token-tree (or (phpinspect--find-innermost-incomplete-class token-tree)
(phpinspect--find-class-token token-tree))))
(inside-class-name
(and inside-class (phpinspect--get-class-name-from-token inside-class))))
(lambda (type)
(phpinspect--type-resolve
types

@ -85,22 +85,19 @@ level of START-FILE in stead of `default-directory`."
(phpinspect--declare-log-group 'bam)
(define-inline phpinspect--log (&rest args)
(defmacro phpinspect--log (&rest args)
(let ((log-group (alist-get (or load-file-name buffer-file-name) phpinspect-log-groups nil nil #'string=)))
(push 'list args)
(inline-quote
(when (and phpinspect--debug
`(when (and phpinspect--debug
(or (not phpinspect-enabled-log-groups)
,(when log-group
(inline-quote
(member (quote ,log-group) phpinspect-enabled-log-groups)))))
`(member (quote ,log-group) phpinspect-enabled-log-groups))))
(with-current-buffer (get-buffer-create "**phpinspect-logs**")
(unless window-point-insertion-type
(set (make-local-variable 'window-point-insertion-type) t))
(goto-char (buffer-end 1))
(insert (concat "[" (format-time-string "%H:%M:%S") "]: "
,(if log-group (concat "(" (symbol-name log-group) ") ") "")
(apply #'format ,args) "\n")))))))
(format ,@args) "\n"))))))
(defun phpinspect-filter-logs (group-name)
(interactive (list (completing-read "Log group: "

@ -29,6 +29,7 @@
(require 'phpinspect-index)
(require 'phpinspect-class)
(require 'phpinspect-queue)
(require 'phpinspect-pipeline)
(defvar phpinspect-worker nil
"Contains the phpinspect worker that is used by all projects.")
@ -124,24 +125,26 @@ already present in the queue."
(while (phpinspect-worker-continue-running worker)
;; This error is used to wake up the thread when new tasks are added to the
;; queue.
(ignore-error 'phpinspect-wakeup-thread
(let* ((task (phpinspect-queue-dequeue (phpinspect-worker-queue worker)))
(mx (make-mutex))
(continue (make-condition-variable mx)))
(if task
;; Execute task if it belongs to a project that has not been
;; purged (meaning that it is still actively used).
(unless (phpinspect-project-purged (phpinspect-task-project task))
(phpinspect-task-execute task worker))
;; else: join with the main thread until wakeup is signaled
(thread-join main-thread))
;; Pause for a second after indexing something, to allow user input to
;; interrupt the thread.
(unless (or (not (input-pending-p))
(phpinspect-worker-skip-next-pause worker))
(phpinspect-thread-pause 1 mx continue))
(setf (phpinspect-worker-skip-next-pause worker) nil))))
(condition-case err
(ignore-error 'phpinspect-wakeup-thread
(let* ((task (phpinspect-queue-dequeue (phpinspect-worker-queue worker)))
(mx (make-mutex))
(continue (make-condition-variable mx)))
(if task
;; Execute task if it belongs to a project that has not been
;; purged (meaning that it is still actively used).
(unless (phpinspect-project-purged (phpinspect-task-project task))
(phpinspect-task-execute task worker))
;; else: join with the main thread until wakeup is signaled
(thread-join main-thread))
;; Pause for a second after indexing something, to allow user input to
;; interrupt the thread.
(unless (or (not (input-pending-p))
(phpinspect-worker-skip-next-pause worker))
(phpinspect-thread-pause 1 mx continue))
(setf (phpinspect-worker-skip-next-pause worker) nil)))
(t (message "Phpinspect worker thread errored :%s" err))))
(phpinspect--log "Worker thread exiting")
(message "phpinspect worker thread exited")))
@ -236,6 +239,7 @@ already present in the queue."
(cl-defmethod phpinspect-task-project ((task phpinspect-index-task))
(phpinspect-index-task-project task))
(cl-defmethod phpinspect-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
@ -265,7 +269,33 @@ already present in the queue."
(when root-index
(phpinspect-project-add-index project root-index)))))))
;;; PARSE BUFFER TASK
;;; INDEX FILE TASK
(cl-defstruct (phpinspect-index-dir-task (:constructor phpinspect-make-index-dir-task))
"A task for the indexation of files"
(project nil
:type phpinspect-project)
(dir nil
:type string))
(cl-defmethod phpinspect-task=
((task1 phpinspect-index-dir-task) (task2 phpinspect-index-dir-task))
(and (eq (phpinspect-index-dir-task-project task1)
(phpinspect-index-dir-task-project task2))
(string= (phpinspect-index-dir-task-dir task1)
(phpinspect-index-dir-task-dir task2))))
(cl-defmethod phpinspect-task-project ((task phpinspect-index-dir-task))
(phpinspect-index-dir-task-project task))
(cl-defmethod phpinspect-task-execute ((task phpinspect-index-dir-task)
(_worker phpinspect-worker))
(phpinspect--log "Entering..")
(let* ((project (phpinspect-index-dir-task-project task))
(fs (phpinspect-project-fs project))
(dir (phpinspect-index-dir-task-dir task)))
(phpinspect--log "Indexing directory %s" dir)
(phpinspect-pipeline (phpinspect-fs-directory-files-recursively fs dir "\\.php$")
:into (phpinspect-project-add-file-index :with-context project))))
(provide 'phpinspect-worker)
;;; phpinspect-worker.el ends here

@ -85,6 +85,7 @@
(defun phpinspect--init-mode ()
"Initialize the phpinspect minor mode for the current buffer."
(phpinspect-ensure-worker)
(setq phpinspect-current-buffer
(phpinspect-make-buffer
:buffer (current-buffer)
@ -103,7 +104,6 @@
(eldoc-add-command 'c-electric-paren)
(eldoc-add-command 'c-electric-backspace)
(phpinspect-ensure-worker)
(phpinspect--after-save-action)
(add-hook 'after-save-hook #'phpinspect--after-save-action nil 'local))
@ -219,9 +219,12 @@ Example configuration:
((looking-back "::[A-Za-z_0-9-]*" nil)
(let ((match (match-string 0)))
(substring match 2 (length match))))
((looking-back "\\$[A-Za-z_0-9-]*" nil)
((looking-back "\\$[A-Za-z_0-9-]" nil)
(let ((match (match-string 0)))
(substring match 1 (length match))))))
(substring match 1 (length match))))
((looking-back "[A-Za-z_0-9-]+" nil t)
(message "Matched string %s" (match-string 0))
(match-string 0))))
((eq command 'post-completion)
(when (eq 'function (phpinspect--completion-kind
(phpinspect--completion-list-get-metadata
@ -338,7 +341,8 @@ before the search is executed."
;; appear frozen while the thread is executing.
(redisplay)
(phpinspect-autoloader-refresh autoloader)))
(phpinspect-autoloader-refresh autoloader)
(phpinspect-project-enqueue-include-dirs project)))
(provide 'phpinspect)

@ -181,3 +181,53 @@ return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->anti
(should (alist-get 'location index1-class))
(should (alist-get 'location index1-class)))))
(ert-deftest phpinspect-index-functions ()
(let* ((code "<?php
use Example\\Thing;
function test_func(): array {}
function example(): Thing {}")
(tokens (phpinspect-parse-string code))
(index (phpinspect--index-tokens tokens))
functions)
(should (setq functions (alist-get 'functions index)))
(should (= 2 (length functions)))
(should (string= "test_func" (phpinspect--function-name (cadr functions))))
(should (string= "example" (phpinspect--function-name (car functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(phpinspect--function-return-type (cadr functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Example\\Thing")
(phpinspect--function-return-type (car functions))))))
(ert-deftest phpinspect-index-functions-in-namespace ()
(let* ((code "<?php
namespace Local;
use Example\\Thing;
function test_func(): array {}
function example(Firewall $wall): Thing {}")
(tokens (phpinspect-parse-string code))
(index (phpinspect--index-tokens tokens))
functions)
(should (setq functions (alist-get 'functions index)))
(should (= 2 (length functions)))
(should (string= "Local\\test_func" (phpinspect--function-name (cadr functions))))
(should (string= "Local\\example" (phpinspect--function-name (car functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(phpinspect--function-return-type (cadr functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Example\\Thing")
(phpinspect--function-return-type (car functions))))
(should (= 3 (length (alist-get 'used-types index))))
(should (member "Firewall" (alist-get 'used-types index)))
(should (member "array" (alist-get 'used-types index)))
(should (member "Thing" (alist-get 'used-types index)))
(should (alist-get 'used-types index))))

Loading…
Cancel
Save