Compare commits

...

22 Commits

Author SHA1 Message Date
Hugo Thunnissen 55b8a0c562 Remove phpinspect-tree
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen b89ef3611f Move resolvecontext to its own file + change tests to use bmap implementation
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen fa2967ddba Optimize incremental parser by skipping over sequential untainted tokens 10 months ago
Hugo Thunnissen 67fd01e68d Bmap approach mostly working now 10 months ago
Hugo Thunnissen fc46349bfe bugs, everywhere
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 4abc3f405a WIP: buffer map approach
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 8e612d76e5 Add parser benchmark
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 0f24f7577f WIP: Performance is terrible for large buffers
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 92ae43fe6e Implement resolvecontext derivation using token metadata tree 10 months ago
Hugo Thunnissen 292b4ca123 Adapt resolvecontext to use metadata tree 10 months ago
Hugo Thunnissen 35a89eb554 Remove already relocated test (see test-edtrack.el)
ci/woodpecker/push/woodpecker Pipeline was successful Details
(+ add a tiny test for the parsing of string tokens)
10 months ago
Hugo Thunnissen 0f2045b107 Add support for token indexation using phpinspect-tree 10 months ago
Hugo Thunnissen a748f32629 test/fix up edit tracker 10 months ago
Hugo Thunnissen 0deb528df1 Remove legacy buffer methods 10 months ago
Hugo Thunnissen feb92c2025 Fix some bugs with phpinspect-slice-detach and phpinspect-tree 10 months ago
Hugo Thunnissen 5e5b73e47d Remove phpinspect-buffer-register-whitespace
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen dfb59591ae Move queue to separate file 10 months ago
Hugo Thunnissen 93f6c702c5 Initial (probably) working implementation of incremental parsing 10 months ago
Hugo Thunnissen f3a5e1d658 Garbonzo 2
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 2e6edd70b2 Garbonzo Gallore
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen bab8deba01 Make interval end explicitly a delimiter
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 08d80d3a38 WIP: n-ary interval tree for token location tracking
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago

File diff suppressed because it is too large Load Diff

@ -0,0 +1,70 @@
(require 'phpinspect-parser)
(defun phpinspect-parse-current-buffer ()
(phpinspect-parse-buffer-until-point
(current-buffer)
(point-max)))
(let ((here (file-name-directory (or load-file-name buffer-file-name))))
(with-temp-buffer
;; (setq-local phpinspect-current-buffer (phpinspect-make-buffer :buffer (current-buffer)))
(insert-file-contents (concat here "/Response.php"))
(message "Incremental parse (warmup):")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t)
(benchmark 1 '(phpinspect-parse-current-buffer)))
(let ((bmap (phpinspect-make-bmap))
(bmap2 (phpinspect-make-bmap)))
(message "Incremental parse:")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap)
(benchmark 1 '(phpinspect-parse-current-buffer)))
(message "Incremental parse (no edits):")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap2 :previous-bmap bmap :edtrack (phpinspect-make-edtrack))
(benchmark 1 '(phpinspect-parse-current-buffer)))
(message "Incremental parse repeat (no edits):")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :previous-bmap bmap2 :edtrack (phpinspect-make-edtrack))
(benchmark 1 '(phpinspect-parse-current-buffer)))
(let ((edtrack (phpinspect-make-edtrack))
(bmap (phpinspect-make-bmap)))
;; Fresh
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap)
(phpinspect-parse-current-buffer))
(message "Incremental parse after buffer edit:")
;; Removes closing curly brace of __construct
(goto-char 9062)
(delete-backward-char 1)
(phpinspect-edtrack-register-edit edtrack 9061 9061 1)
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :previous-bmap bmap :edtrack edtrack)
(benchmark 1 '(phpinspect-parse-current-buffer)))))
;; (message "Metadata parse:")
;; (benchmark 1 '(phpinspect-buffer-parse phpinspect-current-buffer))
(message "Bare (no token reuse) parse (warmup):")
(benchmark 1 '(phpinspect-parse-current-buffer))
(message "Bare (no token reuse) parse:")
(benchmark 1 '(phpinspect-parse-current-buffer))))
;; (goto-char (floor (/ (point-max) 2 )))
;; (insert "abc")
;; (phpinspect-buffer-register-edit phpinspect-current-buffer (- (point) 3) (point) 0)
;; (message "Metadata parse incremental:")
;; (benchmark 1 '(phpinspect-buffer-parse phpinspect-current-buffer))))

@ -0,0 +1,359 @@
;;; phpinspect-bmap.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(cl-defstruct (phpinspect-bmap (:constructor phpinspect-make-bmap))
(starts (make-hash-table :test #'eql
:size (floor (/ (point-max) 4))
:rehash-size 1.5))
(ends (make-hash-table :test #'eql
:size (floor (/ (point-max) 4))
:rehash-size 1.5))
(meta (make-hash-table :test #'eq
:size (floor (/ (point-max) 4))
:rehash-size 1.5))
(token-stack nil
:type list)
(overlays nil
:type list)
(last-token-start nil
:type integer))
(defsubst phpinspect-make-region (start end)
(list start end))
(defalias 'phpinspect-region-start #'car)
(defalias 'phpinspect-region-end #'cadr)
(defsubst phpinspect-region-size (region)
(- (phpinspect-region-end region) (phpinspect-region-start region)))
(defsubst phpinspect-region> (reg1 reg2)
(> (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defsubst phpinspect-region< (reg1 reg2)
(< (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defsubst phpinspect-region-overlaps-point (reg point)
(and (> (phpinspect-region-end reg) point)
(<= (phpinspect-region-start reg) point)))
(defsubst phpinspect-region-overlaps (reg1 reg2)
(or (phpinspect-region-reg2s-point reg1 (phpinspect-region-start reg2))
(phpinspect-region-reg2s-point reg1 (- (phpinspect-region-end reg2) 1))
(phpinspect-region-reg2s-point reg2 (phpinspect-region-start reg1))
(phpinspect-region-reg2s-point reg2 (- (phpinspect-region-end reg1) 1))))
(defsubst phpinspect-region-encloses (reg1 reg2)
(and (<= (phpinspect-region-start reg1) (phpinspect-region-start reg2))
(>= (phpinspect-region-end reg1) (phpinspect-region-end reg2))))
(defsubst phpinspect-make-meta (parent start end whitespace-before token &optional overlay right-siblings)
(list 'meta parent start end whitespace-before token overlay right-siblings))
(defsubst phpinspect-meta-parent (meta)
(cadr meta))
(gv-define-setter phpinspect-meta-end (end meta) `(setcar (cdddr ,meta) ,end))
(gv-define-setter phpinspect-meta-start (start meta) `(setcar (cddr ,meta) ,start))
(gv-define-setter phpinspect-meta-overlay (overlay meta) `(setcar (nthcdr 6 ,meta) ,overlay))
(gv-define-setter phpinspect-meta-parent (parent meta) `(setcar (cdr ,meta) ,parent))
(gv-define-setter phpinspect-meta-right-siblings (siblings meta) `(setcar (nthcdr 7 ,meta) ,siblings))
(defsubst phpinspect-meta-right-siblings (meta)
(car (nthcdr 7 meta)))
(defsubst phpinspect-meta-overlay (meta)
(car (nthcdr 6 meta)))
(defsubst phpinspect-meta-token (meta)
(car (nthcdr 5 meta)))
(defsubst phpinspect-meta-end (meta)
(cadddr meta))
(defsubst phpinspect-meta-width (meta)
(- (phpinspect-meta-end meta) (phpinspect-meta-start meta)))
(defun phpinspect-meta-sort-width (meta1 meta2)
(< (phpinspect-meta-width meta1) (phpinspect-meta-width meta2)))
(defsubst phpinspect-meta-start (meta)
(caddr meta))
(defsubst phpinspect-meta-overlaps-point (meta point)
(and (> (phpinspect-meta-end meta) point)
(<= (phpinspect-meta-start meta) point)))
(defsubst phpinspect-meta-find-parent-matching-token (meta predicate)
(if (funcall predicate (phpinspect-meta-token meta))
meta
(catch 'found
(while (phpinspect-meta-parent meta)
(setq meta (phpinspect-meta-parent meta))
(when (funcall predicate (phpinspect-meta-token meta))
(throw 'found meta))))))
(gv-define-setter phpinspect-overlay-end (end overlay) `(setcar (cddr ,overlay) ,end))
(gv-define-setter phpinspect-overlay-start (start overlay) `(setcar (cdr ,overlay) ,start))
(gv-define-setter phpinspect-overlay-delta (delta overlay) `(setcar (cdddr ,overlay) ,delta))
(defsubst phpinspect-overlay-bmap (overlay)
(car (nthcdr 4 overlay)))
(defsubst phpinspect-overlay-delta (overlay)
(cadddr overlay))
(defsubst phpinspect-overlay-start (overlay)
(cadr overlay))
(defsubst phpinspect-overlay-end (overlay)
(caddr overlay))
(defsubst phpinspect-overlay-token-meta (overlay)
(car (nthcdr 5 overlay)))
(defsubst phpinspect-overlay-overlaps-point (overlay point)
(and (> (phpinspect-overlay-end overlay) point)
(<= (phpinspect-overlay-start overlay) point)))
(defmacro phpinspect-bmap-iterate-region (region place-and-bmap &rest body)
(declare (indent defun))
(let ((place (car place-and-bmap))
(bmap (gensym))
(bmap-stack (gensym))
(region-start (gensym))
(region-end (gensym)))
`(let ((,bmap)
(,bmap-stack (list ,(cadr place-and-bmap)))
(,region-start (car ,region))
(,region-end (cadr ,region)))
(while (setq ,bmap (pop ,bmap-stack))
(phpinspect-bmap-iterate (,place ,bmap)
(when (and (<= ,region-start
(phpinspect-meta-start ,place))
(>= ,region-end
(phpinspect-meta-end ,place)))
,@body))))))
(defmacro phpinspect-bmap-iterate (place-and-bmap &rest body)
(declare (indent defun))
(let ((place (car place-and-bmap))
(bmap (gensym))
(bmap-stack (gensym))
(_ignored (gensym))
(overlay-start (gensym))
(overlay-end (gensym)))
`(let ((,bmap-stack (list ,(cadr place-and-bmap)))
(,bmap))
(while (setq ,bmap (pop ,bmap-stack))
(if (phpinspect-overlay-p ,bmap)
(let ((,overlay-start (phpinspect-overlay-start ,bmap))
(,overlay-end (phpinspect-overlay-end ,bmap)))
(maphash (lambda (,_ignored ,place)
(setq ,place (phpinspect-overlay-wrap-meta ,bmap ,place))
(when (and (<= ,overlay-start
(phpinspect-meta-start ,place))
(>= ,overlay-end
(phpinspect-meta-end ,place)))
(if (phpinspect-meta-overlay ,place)
(push (phpinspect-meta-overlay ,place) ,bmap-stack)
,@body)))
(phpinspect-bmap-meta (phpinspect-overlay-bmap ,bmap))))
(maphash (lambda (,_ignored ,place)
(if (phpinspect-meta-overlay ,place)
(push (phpinspect-meta-overlay ,place) ,bmap-stack)
,@body))
(phpinspect-bmap-meta ,bmap)))))))
(defsubst phpinspect-bmap-register (bmap start end token &optional whitespace-before overlay)
(let* ((starts (phpinspect-bmap-starts bmap))
(ends (phpinspect-bmap-ends bmap))
(meta (phpinspect-bmap-meta bmap))
(last-token-start (phpinspect-bmap-last-token-start bmap))
(existing-end (gethash end ends))
(token-meta (phpinspect-make-meta nil start end whitespace-before token overlay)))
(unless whitespace-before
(setq whitespace-before ""))
(puthash start token-meta starts)
(if existing-end
(push token existing-end)
(puthash end (list token-meta) ends))
(puthash token token-meta meta)
(when (and last-token-start
(<= start last-token-start))
(let ((child)
(stack (phpinspect-bmap-token-stack bmap))
(right-siblings))
(while (and (car stack) (>= (phpinspect-meta-start (car stack))
start))
(setq child (pop stack))
(setf (phpinspect-meta-parent child) token-meta)
(when (phpinspect-meta-overlay child)
(setf (phpinspect-meta-parent
(phpinspect-overlay-token-meta
(phpinspect-meta-overlay child)))
token-meta))
(setf (phpinspect-meta-right-siblings child) right-siblings)
(when (phpinspect-meta-overlay child)
(setf (phpinspect-meta-right-siblings
(phpinspect-overlay-token-meta
(phpinspect-meta-overlay child)))
right-siblings))
(push (phpinspect-meta-token child) right-siblings))
(setf (phpinspect-bmap-token-stack bmap) stack)))
(setf (phpinspect-bmap-last-token-start bmap) start)
(push token-meta (phpinspect-bmap-token-stack bmap))))
(defsubst phpinspect-overlay-p (overlay)
(and (listp overlay)
(eq 'overlay (car overlay))))
(defsubst phpinspect-overlay-wrap-meta (overlay meta)
(when meta
(setq meta (cl-copy-list meta))
(setf (phpinspect-meta-start meta)
(+ (phpinspect-meta-start meta) (phpinspect-overlay-delta overlay)))
(setf (phpinspect-meta-end meta)
(+ (phpinspect-meta-end meta) (phpinspect-overlay-delta overlay)))
(when (phpinspect-meta-overlay meta)
(let ((meta-overlay (cl-copy-list (phpinspect-meta-overlay meta))))
(setf (phpinspect-overlay-start meta-overlay)
(+ (phpinspect-overlay-start meta-overlay)
(phpinspect-overlay-delta overlay)))
(setf (phpinspect-overlay-end meta-overlay)
(+ (phpinspect-overlay-end meta-overlay)
(phpinspect-overlay-delta overlay)))
(setf (phpinspect-overlay-delta meta-overlay)
(+ (phpinspect-overlay-delta meta-overlay)
(phpinspect-overlay-delta overlay)))
(setf (phpinspect-meta-overlay meta) meta-overlay)))
meta))
(cl-defmethod phpinspect-bmap-token-starting-at ((overlay (head overlay)) point)
(phpinspect-overlay-wrap-meta
overlay
(phpinspect-bmap-token-starting-at
(phpinspect-overlay-bmap overlay) (- point (phpinspect-overlay-delta overlay)))))
(cl-defmethod phpinspect-bmap-token-starting-at ((bmap phpinspect-bmap) point)
(let ((overlay (phpinspect-bmap-overlay-at-point bmap point)))
(if overlay
(phpinspect-bmap-token-starting-at overlay point)
(gethash point (phpinspect-bmap-starts bmap)))))
(cl-defmethod phpinspect-bmap-tokens-ending-at ((overlay (head overlay)) point)
(mapcar (lambda (meta) (phpinspect-overlay-wrap-meta overlay meta))
(phpinspect-bmap-tokens-ending-at
(phpinspect-overlay-bmap overlay) (- point (phpinspect-overlay-delta overlay)))))
(cl-defmethod phpinspect-bmap-tokens-ending-at ((bmap phpinspect-bmap) point)
(let ((overlay (phpinspect-bmap-overlay-at-point bmap point)))
(if overlay
(phpinspect-bmap-tokens-ending-at overlay point)
(gethash point (phpinspect-bmap-ends bmap)))))
(defsubst phpinspect-bmap-overlay-at-point (bmap point)
(catch 'found
(dolist (overlay (phpinspect-bmap-overlays bmap))
(when (phpinspect-overlay-overlaps-point overlay point)
(throw 'found overlay)))))
(defsubst phpinspect-bmap-tokens-overlapping (bmap point)
(let ((tokens))
(phpinspect-bmap-iterate (meta bmap)
(when (phpinspect-meta-overlaps-point meta point)
(push meta tokens)))
(sort tokens #'phpinspect-meta-sort-width)))
(cl-defmethod phpinspect-bmap-token-meta ((overlay (head overlay)) token)
(phpinspect-bmap-token-meta (phpinspect-overlay-bmap overlay) token))
(cl-defmethod phpinspect-bmap-token-meta ((bmap phpinspect-bmap) token)
(or (gethash token (phpinspect-bmap-meta bmap))
(let ((found?))
(catch 'found
(dolist (overlay (phpinspect-bmap-overlays bmap))
(when (setq found? (phpinspect-bmap-token-meta overlay token))
(throw 'found found?)))))))
(defsubst phpinspect-probably-token-p (token)
(and (listp token)
(symbolp (car token))))
(defsubst phpinspect-bmap-last-token-before-point (bmap point)
(let* ((ends (phpinspect-bmap-ends bmap))
(ending))
(unless (hash-table-empty-p ends)
(while (not (or (<= point 0) (setq ending (phpinspect-bmap-tokens-ending-at bmap point))))
(setq point (- point 1)))
(car (last ending)))))
(defsubst phpinspect-bmap-overlay (bmap bmap-overlay token-meta pos-delta &optional whitespace-before)
(let* ((overlays (phpinspect-bmap-overlays bmap))
(start (+ (phpinspect-meta-start token-meta) pos-delta))
(end (+ (phpinspect-meta-end token-meta) pos-delta))
(overlay `(overlay ,start ,end ,pos-delta ,bmap-overlay ,token-meta))
(before))
(phpinspect-bmap-register bmap start end (phpinspect-meta-token token-meta) whitespace-before overlay)
(if overlays
(progn
(catch 'break
(while (setq before (car overlays))
(if (> (phpinspect-overlay-start overlay) (phpinspect-overlay-end before))
(throw 'break nil)
(setq overlays (cdr overlays)))))
(if (and before (cdr overlays))
;; Append after
(progn
(setcdr overlays (cons overlay (cdr overlays))))
;; Append at end of overlay list
(nconc (phpinspect-bmap-overlays bmap) (list overlay))))
;; No exising overlays, overwrite
(push overlay (phpinspect-bmap-overlays bmap)))))
(defun phpinspect-bmap-make-location-resolver (bmap)
(lambda (token)
(let ((meta (phpinspect-bmap-token-meta bmap token)))
(if meta
(phpinspect-make-region (phpinspect-meta-start meta)
(phpinspect-meta-end meta))
(phpinspect-make-region 0 0)))))
(provide 'phpinspect-bmap)
;;; phpinspect-bmap.el ends here

@ -23,72 +23,74 @@
;;; Code:
;;(require 'phpinspect-tree)
(require 'phpinspect-bmap)
(require 'phpinspect-edtrack)
(defvar-local phpinspect-current-buffer nil
"An instance of `phpinspect-buffer' local to the active
buffer. This variable is only set for buffers where
`phpinspect-mode' is active. Also see `phpinspect-buffer'.")
(defsubst phpinspect-make-region (start end)
(list start end))
(defalias 'phpinspect-region-start #'car)
(defalias 'phpinspect-region-end #'cadr)
(defsubst phpinspect-region-size (region)
(- (phpinspect-region-end region) (phpinspect-region-start region)))
(defsubst phpinspect-region> (reg1 reg2)
(> (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defsubst phpinspect-region< (reg1 reg2)
(< (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(cl-defstruct (phpinspect-buffer (:constructor phpinspect-make-buffer))
"An object containing phpinspect related metadata linked to an
emacs buffer."
(buffer nil
:type buffer
:documentation "The underlying emacs buffer")
(location-map (make-hash-table :test 'eq :size 400 :rehash-size 400)
:type hash-table
:documentation
"A map that lets us look up the character
positions of a token within this buffer.")
:documentation "The associated emacs buffer")
(tree nil
:type list
:documentation
"An instance of a token tree as returned by
`phpinspect--index-tokens'. Meant to be eventually consistent
with the contents of the buffer."))
"Parsed token tree that resulted from last parse")
(map nil
:type phpinspect-bmap)
(edit-tracker (phpinspect-make-edtrack)
:type phpinspect-edtrack))
(cl-defmethod phpinspect-buffer-parse ((buffer phpinspect-buffer))
"Parse the PHP code in the the emacs buffer that this object is
linked with."
(with-current-buffer (phpinspect-buffer-buffer buffer)
(setf (phpinspect-buffer-location-map buffer)
(make-hash-table :test 'eq
:size 400
:rehash-size 400))
(let ((tree (phpinspect-parse-current-buffer)))
(setf (phpinspect-buffer-tree buffer) tree)
tree)))
(cl-defmethod phpinspect-buffer-token-location ((buffer phpinspect-buffer) token)
(gethash token (phpinspect-buffer-location-map buffer)))
(if (or (not (phpinspect-buffer-tree buffer))
(phpinspect-edtrack-taint-pool (phpinspect-buffer-edit-tracker buffer)))
(with-current-buffer (phpinspect-buffer-buffer buffer)
(let* ((map (phpinspect-make-bmap))
(buffer-map (phpinspect-buffer-map buffer))
(ctx (phpinspect-make-pctx
:bmap map
:incremental t
:previous-bmap buffer-map
:edtrack (phpinspect-buffer-edit-tracker buffer))))
(phpinspect-with-parse-context ctx
(let ((parsed (phpinspect-parse-current-buffer)))
(setf (phpinspect-buffer-map buffer) map)
(setf (phpinspect-buffer-tree buffer) parsed)
(phpinspect-edtrack-clear (phpinspect-buffer-edit-tracker buffer))
;; return
parsed))))
;; Else: Just return last parse result
(phpinspect-buffer-tree buffer)))
(cl-defmethod phpinspect-buffer-reparse ((buffer phpinspect-buffer))
(setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap))
(phpinspect-buffer-parse buffer))
(defsubst phpinspect-buffer-parse-map (buffer)
(phpinspect-buffer-parse buffer)
(phpinspect-buffer-map buffer))
(cl-defmethod phpinspect-buffer-register-edit
((buffer phpinspect-buffer) (start integer) (end integer) (pre-change-length integer))
(phpinspect-edtrack-register-edit
(phpinspect-buffer-edit-tracker buffer) start end pre-change-length))
(cl-defmethod phpinspect-buffer-tokens-enclosing-point ((buffer phpinspect-buffer) point)
(let ((tokens))
(maphash
(lambda (token region)
(when (and (<= (phpinspect-region-start region) point)
(>= (phpinspect-region-end region) point))
(push token tokens)))
(phpinspect-buffer-location-map buffer))
(sort tokens (lambda (tok1 tok2)
(phpinspect-region< (phpinspect-buffer-token-location tok1)
(phpinspect-buffer-token-location tok2))))))
(phpinspect-bmap-tokens-overlapping (phpinspect-buffer-map buffer) point))
(cl-defmethod phpinspect-buffer-token-meta ((buffer phpinspect-buffer) token)
(phpinspect-bmap-token-meta (phpinspect-buffer-map buffer) token))
(cl-defmethod phpinspect-buffer-location-resover ((buffer phpinspect-buffer))
(phpinspect-bmap-make-location-resolver (phpinspect-buffer-map buffer)))
(provide 'phpinspect-buffer)

@ -0,0 +1,198 @@
;;; phpinspect-edtrack.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(cl-defstruct (phpinspect-edtrack (:constructor phpinspect-make-edtrack))
(edits nil
:type list)
(taint-pool nil
:type list))
(defsubst phpinspect-edtrack-make-taint-iterator (track)
(cons (car (phpinspect-edtrack-taint-pool track))
(cl-copy-list (cdr (phpinspect-edtrack-taint-pool track)))))
(gv-define-setter phpinspect-taint-iterator-current (current iter) `(setcar ,iter ,current))
(defsubst phpinspect-taint-iterator-current (iter)
(car iter))
(defsubst phpinspect-taint-iterator-follow (iter pos)
(or (while (and (phpinspect-taint-iterator-current iter)
(> pos (phpinspect-taint-end
(phpinspect-taint-iterator-current iter))))
(setf (phpinspect-taint-iterator-current iter) (pop (cdr iter))))
(phpinspect-taint-iterator-current iter)))
(defsubst phpinspect-taint-iterator-token-is-tainted-p (iter meta)
(and (phpinspect-taint-iterator-follow iter (phpinspect-meta-start meta))
(phpinspect-taint-overlaps-meta
(phpinspect-taint-iterator-current iter) meta)))
(defsubst phpinspect-taint-iterator-region-is-tainted-p (iter start end)
(and (phpinspect-taint-iterator-follow iter start)
(phpinspect-taint-overlaps-region
(phpinspect-taint-iterator-current iter) start end)))
(defsubst phpinspect-edit-original-end (edit)
(or (caar edit) 0))
(defsubst phpinspect-edit-end (edit)
(if edit
(let ((end (or (caar edit) 0))
(delta 0)
(previous-edit (cdr edit)))
(+ end (phpinspect-edit-delta previous-edit)))))
(defsubst phpinspect-edit-delta (edit)
(let ((delta (or (cdar edit) 0))
(previous-edit edit))
(while (setq previous-edit (cdr previous-edit))
(setq delta (+ delta (cdar previous-edit))))
delta))
(defsubst phpinspect-edtrack-original-position-at-point (track point)
(let ((edit (phpinspect-edtrack-edits track)))
(while (and edit (< point (phpinspect-edit-end edit)))
(setq edit (cdr edit)))
(- point (phpinspect-edit-delta edit))))
(defsubst phpinspect-edtrack-current-position-at-point (track point)
(let ((edit (phpinspect-edtrack-edits track)))
(while (and edit (< point (phpinspect-edit-original-end edit)))
(setq edit (cdr edit)))
(+ point (phpinspect-edit-delta edit))))
(defsubst phpinspect-edtrack-register-edit (track start end pre-change-length)
(let ((edit-before (phpinspect-edtrack-edits track)))
(while (and edit-before (< end (phpinspect-edit-end edit-before)))
(setq edit-before (cdr edit-before)))
(phpinspect-edtrack-register-taint
track
(phpinspect-edtrack-original-position-at-point track start)
(phpinspect-edtrack-original-position-at-point track end))
(let* ((new-edit (cons
;; The end location of the edited region, before being
;; edited, with the delta edits that happened at preceding
;; points in the buffer subtratted. This corresponds with
;; the original position of the region end before the
;; buffer was ever edited.
(- (+ start pre-change-length) (or (phpinspect-edit-delta edit-before) 0))
;; The delta of this edit.
(- (- end start) pre-change-length))))
(if edit-before
(progn
(setcdr edit-before (cons (car edit-before) (cdr edit-before)))
(setcar edit-before new-edit))
(if (phpinspect-edtrack-edits track)
(push new-edit (cdr (last (phpinspect-edtrack-edits track))))
(push new-edit (phpinspect-edtrack-edits track)))))))
(defsubst phpinspect-taint-start (taint)
(car taint))
(defsubst phpinspect-taint-end (taint)
(cdr taint))
(defsubst phpinspect-make-taint (start end)
(cons start end))
(defsubst phpinspect-taint-overlaps-point (taint point)
(and (> (phpinspect-taint-end taint) point)
(<= (phpinspect-taint-start taint) point)))
(defsubst phpinspect-taint-overlaps-region (taint start end)
(or (phpinspect-taint-overlaps-point taint start)
(phpinspect-taint-overlaps-point taint end)
(and (> end (phpinspect-taint-start taint))
(<= start (phpinspect-taint-start taint)))
(and (> end (phpinspect-taint-end taint))
(<= start (phpinspect-taint-end taint)))))
(defsubst phpinspect-taint-overlaps (taint1 taint2)
(or (phpinspect-taint-overlaps-point taint1 (phpinspect-taint-start taint2))
(phpinspect-taint-overlaps-point taint1 (phpinspect-taint-end taint2))
(phpinspect-taint-overlaps-point taint2 (phpinspect-taint-start taint1))
(phpinspect-taint-overlaps-point taint2 (phpinspect-taint-end taint1))))
(defsubst phpinspect-taint-overlaps-meta (taint meta)
(or (phpinspect-taint-overlaps-point taint (phpinspect-meta-start meta))
(phpinspect-taint-overlaps-point taint (phpinspect-meta-end meta))
(phpinspect-meta-overlaps-point meta (phpinspect-taint-start taint))
(phpinspect-meta-overlaps-point meta (phpinspect-taint-end taint))))
(defsubst phpinspect-edtrack-clear-taint-pool (track)
(setf (phpinspect-edtrack-taint-pool track) nil))
(defsubst phpinspect-edtrack-clear (track)
(setf (phpinspect-edtrack-edits track) nil)
(phpinspect-edtrack-clear-taint-pool track))
(defsubst phpinspect-edtrack-register-taint (track start end)
(let ((pool (phpinspect-edtrack-taint-pool track))
(idx 0)
(overlap-start)
(overlap-end)
(left-neighbour)
(taint (phpinspect-make-taint start end)))
(catch 'break
(while pool
(if (phpinspect-taint-overlaps taint (car pool))
(progn
(when (< (phpinspect-taint-start (car pool)) start)
(setcar taint (phpinspect-taint-start (car pool))))
(when (> (phpinspect-taint-end (car pool)) end)
(setcdr taint (phpinspect-taint-end (car pool))))
(when (not overlap-start)
(setq overlap-start idx))
(setq overlap-end idx))
;; Else
(when overlap-start
(throw 'break nil))
(when (> start (phpinspect-taint-end (car pool)))
(setq left-neighbour pool)
(throw 'break nil)))
(setq pool (cdr pool)
idx (+ idx 1))))
(cond (overlap-start
(setq pool (phpinspect-edtrack-taint-pool track))
(setcar (nthcdr overlap-start pool) taint)
(setcdr (nthcdr overlap-start pool) (nthcdr (+ 1 overlap-end) pool)))
(left-neighbour
(setcdr left-neighbour (cons taint (cdr left-neighbour))))
(t
(push taint (phpinspect-edtrack-taint-pool track))))))
(provide 'phpinspect-edtrack)
;;; phpinspect-edtrack.el ends here

@ -45,24 +45,24 @@ buffer position to insert the use statement at."
(setq fqn (string-trim-left fqn "\\\\")))
(if namespace-token
(let* ((region (gethash
namespace-token (phpinspect-buffer-location-map buffer)))
(let* ((meta (phpinspect-bmap-token-meta
(phpinspect-buffer-map buffer) namespace-token))
(existing-use (seq-find #'phpinspect-use-p
(phpinspect-namespace-body namespace-token)))
(namespace-block (phpinspect-namespace-block namespace-token)))
(if existing-use
(phpinspect-insert-at-point
(phpinspect-region-start
(phpinspect-buffer-token-location buffer existing-use))
(phpinspect-meta-start
(phpinspect-buffer-token-meta buffer existing-use))
(format "use %s;%c" fqn ?\n))
(if namespace-block
(phpinspect-insert-at-point
(+ 1 (phpinspect-region-start
(phpinspect-buffer-token-location buffer namespace-block)))
(+ 1 (phpinspect-meta-start
(phpinspect-buffer-token-meta buffer namespace-block)))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-insert-at-point
(phpinspect-region-end
(phpinspect-buffer-token-location
(phpinspect-meta-end
(phpinspect-buffer-token-meta
buffer (seq-find #'phpinspect-terminator-p namespace-token)))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n)))))
;; else
@ -70,20 +70,20 @@ buffer position to insert the use statement at."
(phpinspect-buffer-tree buffer))))
(if existing-use
(phpinspect-insert-at-point
(phpinspect-region-start
(phpinspect-buffer-token-location buffer existing-use))
(phpinspect-meta-start
(phpinspect-buffer-token-meta buffer existing-use))
(format "use %s;%c" fqn ?\n))
(let ((first-token (cadr (phpinspect-buffer-tree buffer))))
(if (and (phpinspect-word-p first-token)
(string= "declare" (cadr first-token)))
(phpinspect-insert-at-point
(phpinspect-region-end
(phpinspect-buffer-token-location
(phpinspect-meta-end
(phpinspect-buffer-token-meta
buffer (seq-find #'phpinspect-terminator-p (phpinspect-buffer-tree buffer))))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-insert-at-point
(phpinspect-region-start
(phpinspect-buffer-token-location buffer first-token))
(phpinspect-meta-start
(phpinspect-buffer-token-meta buffer first-token))
(format "%c%cuse %s;%c%c" ?\n ?\n fqn ?\n ?\n))))))))
(defun phpinspect-add-use-interactive (typename buffer project &optional namespace-token)
@ -110,9 +110,9 @@ that there are import (\"use\") statements for them."
(interactive)
(if phpinspect-current-buffer
(let* ((tree (phpinspect-buffer-parse phpinspect-current-buffer))
(location-map (phpinspect-buffer-location-map phpinspect-current-buffer))
(index (phpinspect--index-tokens
tree nil (lambda (token) (gethash token location-map))))
tree nil (phpinspect-buffer-location-resolver
phpinspect-current-buffer)))
(classes (alist-get 'classes index))
(imports (alist-get 'imports index))
(project (phpinspect--cache-get-project-create
@ -121,12 +121,22 @@ that there are import (\"use\") statements for them."
(dolist (class classes)
(let* ((class-imports (alist-get 'imports class))
(used-types (alist-get 'used-types class))
(region (alist-get 'location class)))
(class-name (alist-get 'class-name class))
(region))
(dolist (type used-types)
;; Retrieve latest version of class location data changes with
;; each added use statement + reindex.
(setq region
(alist-get 'location
(phpinspect-index-get-class
index class-name)))
(let ((namespace
(seq-find #'phpinspect-namespace-p
(phpinspect-buffer-tokens-enclosing-point
phpinspect-current-buffer (phpinspect-region-start region)))))
phpinspect-current-buffer (phpinspect-meta-start meta)))))
;; Add use statements for types that aren't imported.
(unless (or (or (alist-get type class-imports)
@ -150,6 +160,11 @@ that there are import (\"use\") statements for them."
;; blocks this could cause problems as a namespace may grow by
;; added import statements and start envelopping the classes
;; below it.
(phpinspect-buffer-parse phpinspect-current-buffer)))))))))
(setq index
(phpinspect--index-tokens
(phpinspect-buffer-parse phpinspect-current-buffer)
nil
(phpinspect-buffer-location-resolver
phpinspect-current-buffer)))))))))))
(provide 'phpinspect-imports)

@ -461,6 +461,12 @@ Return value is a list of the types that are \"newed\"."
project-root)))
(phpinspect-project-get-class-create project class-fqn))))
(cl-defmethod phpinspect-index-get-class
((index (head phpinspect--root-index) (class-name phpinspect--type)))
(alist-get class-name (alist-get 'classes index)
nil nil #'phpinspect--type=))
(defun phpinspect-index-current-buffer ()
"Index a PHP file for classes and the methods they have"
(phpinspect--index-tokens (phpinspect-parse-current-buffer)))

@ -23,6 +23,10 @@
;;; Code:
;;(require 'phpinspect-tree)
(require 'phpinspect-edtrack)
(require 'phpinspect-bmap)
(defvar phpinspect-parser-obarray (obarray-make)
"An obarray containing symbols for all phpinspect (sub)parsers.")
@ -50,7 +54,11 @@
(documentation (intern handler-name phpinspect-handler-obarray))))
(pop-to-buffer (current-buffer))))
(defsubst phpinspect--strip-last-char (string)
(defsubst phpinspect--strip-word-end-space (string)
(when phpinspect-parse-context
(phpinspect-pctx-register-whitespace
phpinspect-parse-context
(substring string (- (length string) 1) (length string))))
(substring string 0 (- (length string) 1)))
(defsubst phpinspect-munch-token-without-attribs (string token-keyword)
@ -173,9 +181,6 @@ Type can be any of the token types returned by
"Get the argument list of a function"
(seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil))
(defun phpinspect-function-block (token)
(cadr token))
(defun phpinspect-annotation-p (token)
(phpinspect-token-type-p token :annotation))
@ -201,8 +206,13 @@ Type can be any of the token types returned by
(or (phpinspect-token-type-p token :array)
(phpinspect-incomplete-array-p token)))
(defsubst phpinspect-incomplete-root-p (token)
(and (phpinspect-root-p token)
(seq-find #'phpinspect-incomplete-token-p (cdr token))))
(defsubst phpinspect-incomplete-token-p (token)
(or (phpinspect-incomplete-class-p token)
(or (phpinspect-incomplete-root-p token)
(phpinspect-incomplete-class-p token)
(phpinspect-incomplete-block-p token)
(phpinspect-incomplete-list-p token)
(phpinspect-incomplete-array-p token)
@ -221,6 +231,18 @@ Type can be any of the token types returned by
(phpinspect-const-p token)
(phpinspect-static-p token)))
(defsubst phpinspect-enclosing-token-p (token)
"Returns t when a token can enclose other tokens"
(or
(phpinspect-list-p token)
(phpinspect-block-p token)
(phpinspect-class-p token)
(phpinspect-function-p token)
(phpinspect-array-p token)
(phpinspect-scope-p token)
(phpinspect-static-p token)
(phpinspect-const-p token)))
(defun phpinspect-namespace-keyword-p (token)
(and (phpinspect-word-p token) (string= (car (last token)) "namespace")))
@ -346,9 +368,14 @@ parser function is then returned in byte-compiled form."
(unless parser-symbol
(error "Phpinspect: No parser found by name %s" name))
(or (symbol-function parser-symbol)
(defalias parser-symbol
(phpinspect-parser-compile (symbol-value parser-symbol))))))
(if (and phpinspect-parse-context
(phpinspect-pctx-incremental phpinspect-parse-context))
(let ((func (phpinspect-parser-compile-incremental (symbol-value parser-symbol))))
(lambda (&rest arguments)
(apply func phpinspect-parse-context arguments)))
(or (symbol-function parser-symbol)
(defalias parser-symbol
(phpinspect-parser-compile (symbol-value parser-symbol)))))))
(defun phpinspect-purge-parser-cache ()
"Unset functions in `phpinspect-parser-obarray`.
@ -360,6 +387,16 @@ have any effect."
(interactive)
(obarray-map #'fmakunbound phpinspect-parser-obarray))
(defmacro phpinspect-pctx-save-whitespace (pctx &rest body)
(declare (indent 1))
(let ((save-sym (gensym)))
`(let ((,save-sym (phpinspect-pctx-whitespace-before ,pctx)))
(unwind-protect
(progn
(setf (phpinspect-pctx-whitespace-before ,pctx) nil)
,@body)
(setf (phpinspect-pctx-whitespace-before ,pctx) ,save-sym)))))
(defun phpinspect-make-parser-function (tree-type handler-list &optional delimiter-predicate)
"Create a parser function using the handlers by names defined in HANDLER-LIST.
@ -390,7 +427,7 @@ token is \";\", which marks the end of a statement in PHP."
(delimiter-predicate (if (symbolp delimiter-predicate)
`(quote ,delimiter-predicate)
delimiter-predicate)))
`(lambda (buffer max-point &optional continue-condition)
`(lambda (buffer max-point &optional continue-condition &rest _ignored)
(with-current-buffer buffer
(let ((tokens)
(delimiter-predicate (when (functionp ,delimiter-predicate) ,delimiter-predicate)))
@ -402,29 +439,233 @@ token is \";\", which marks the end of a statement in PHP."
(cond ,@(mapcar
(lambda (handler)
`((looking-at ,(plist-get (symbol-value handler) 'regexp))
(let ((start-position (point))
(token (funcall ,(symbol-function handler)
(let ((token (funcall ,(symbol-function handler)
(match-string 0)
max-point)))
(when token
(if (null tokens)
(setq tokens (list token))
(progn
(nconc tokens (list token))))
;; When parsing within a buffer that has
;; `phpinspect-current-buffer` set, update the
;; token location map. Usually, this variable
;; is set when `phpinspect-mode` is active.
(when phpinspect-current-buffer
(puthash token
(phpinspect-make-region start-position
(point))
(phpinspect-buffer-location-map
phpinspect-current-buffer)))))))
(nconc tokens (list token))))))))
handlers)
(t (forward-char))))
(push ,tree-type tokens))))))
(push ,tree-type tokens)
;; Return
tokens)))))
(defvar phpinspect-parse-context nil
"An instance of `phpinspect-pctx' that is used when
parsing. Usually used in combination with
`phpinspect-with-parse-context'")
(defmacro phpinspect-with-parse-context (ctx &rest body)
(declare (indent 1))
(let ((old-ctx phpinspect-parse-context))
`(unwind-protect
(progn
(setq phpinspect-parse-context ,ctx)
,@body)
(setq phpinspect-parse-context ,old-ctx))))
(cl-defstruct (phpinspect-pctx (:constructor phpinspect-make-pctx))
"Parser Context"
(incremental nil)
(edtrack nil
:type phpinspect-edtrack)
(bmap (phpinspect-make-bmap)
:type phpinspect-bmap)
(previous-bmap nil
:type phpinspect-bmap)
(whitespace-before ""
:type string))
(defsubst phpinspect-pctx-register-token (pctx token start end)
(phpinspect-bmap-register
(phpinspect-pctx-bmap pctx) start end token (phpinspect-pctx-consume-whitespace pctx)))
(defsubst phpinspect-pctx-register-whitespace (pctx whitespace)
(setf (phpinspect-pctx-whitespace-before pctx) whitespace))
(defsubst phpinspect-pctx-consume-whitespace (pctx)
(let ((whitespace (phpinspect-pctx-whitespace-before pctx)))
(setf (phpinspect-pctx-whitespace-before pctx) "")
whitespace))
(defun phpinspect-make-bmap-parser-function (tree-type handler-list &optional delimiter-predicate)
"Like `phpinspect-make-parser-function', but returned function is able to reuse an already parsed tree."
(let ((handlers (mapcar
(lambda (handler-name)
(let* ((handler-name (symbol-name handler-name))
(handler (intern-soft handler-name phpinspect-handler-obarray)))
(if handler
handler
(error "No handler found by name \"%s\"" handler-name))))
handler-list))
(delimiter-predicate (if (symbolp delimiter-predicate)
`(quote ,delimiter-predicate)
delimiter-predicate)))
`(lambda (context buffer max-point &optional continue-condition root)
(with-current-buffer buffer
(let* ((tokens)
(root-start (point))
(bmap (phpinspect-pctx-bmap context))
(previous-bmap (phpinspect-pctx-previous-bmap context))
(edtrack (phpinspect-pctx-edtrack context))
(taint-iterator (when edtrack (phpinspect-edtrack-make-taint-iterator edtrack)))
(delimiter-predicate (when (functionp ,delimiter-predicate) ,delimiter-predicate)))
(phpinspect-pctx-save-whitespace context
(while (and (< (point) max-point)
(if continue-condition (funcall continue-condition) t)
(not (if delimiter-predicate
(funcall delimiter-predicate (car (last tokens)))
nil)))
(cond ,@(mapcar
(lambda (handler)
`((looking-at ,(plist-get (symbol-value handler) 'regexp))
(let* ((match (match-string 0))
(start-position (point))
(original-position
(when (and previous-bmap edtrack)
(phpinspect-edtrack-original-position-at-point edtrack start-position)))
(existing-meta)
(current-end-position)
(token))
(when (and previous-bmap edtrack)
(setq existing-meta (phpinspect-bmap-token-starting-at previous-bmap original-position))
(when existing-meta
(setq current-end-position (phpinspect-edtrack-current-position-at-point
edtrack (phpinspect-meta-end existing-meta)))))
(if (and existing-meta
(not (or (phpinspect-root-p (phpinspect-meta-token existing-meta))
(phpinspect-taint-iterator-token-is-tainted-p taint-iterator existing-meta))))
(progn
(setq token (phpinspect-meta-token existing-meta))
;; Re-register existing token
(let ((delta (- start-position original-position)))
(phpinspect-bmap-overlay
bmap previous-bmap existing-meta delta
(phpinspect-pctx-consume-whitespace context)))
(goto-char current-end-position))
(progn
(setq token (funcall ,(symbol-function handler) match max-point))
(when token
(phpinspect-pctx-register-token context token start-position (point)))))
(when token
(if (null tokens)
(setq tokens (list token))
(progn
(nconc tokens (list token))))))))
handlers)
(t (forward-char)))))
(push ,tree-type tokens)
(when root
(phpinspect-pctx-register-token context tokens root-start (point)))
;; Return
tokens)))))
(defun phpinspect-make-incremental-parser-function (tree-type handler-list &optional delimiter-predicate)
"Like `phpinspect-make-parser-function', but returned function is able to reuse an already parsed tree."
(let ((handlers (mapcar
(lambda (handler-name)
(let* ((handler-name (symbol-name handler-name))
(handler (intern-soft handler-name phpinspect-handler-obarray)))
(if handler
handler
(error "No handler found by name \"%s\"" handler-name))))
handler-list))
(delimiter-predicate (if (symbolp delimiter-predicate)
`(quote ,delimiter-predicate)
delimiter-predicate)))
`(lambda (context buffer max-point &optional continue-condition root)
(with-current-buffer buffer
(let* ((tokens (list ,tree-type))
(root-start (point))
(bmap (phpinspect-pctx-bmap context))
(previous-bmap (phpinspect-pctx-previous-bmap context))
(edtrack (phpinspect-pctx-edtrack context))
(taint-iterator (when edtrack (phpinspect-edtrack-make-taint-iterator edtrack)))
;; Loop variables
(start-position)
(original-position)
(current-end-position)
(existing-meta)
(token)
(delimiter-predicate (when (functionp ,delimiter-predicate) ,delimiter-predicate)))
(phpinspect-pctx-save-whitespace context
(while (and (< (point) max-point)
(if continue-condition (funcall continue-condition) t)
(not (if delimiter-predicate
(funcall delimiter-predicate (car (last tokens)))
nil)))
(setq start-position (point))
(cond ((and previous-bmap edtrack
(setq existing-meta
(phpinspect-bmap-token-starting-at
previous-bmap
(setq original-position
(phpinspect-edtrack-original-position-at-point edtrack start-position))))
(not (or (phpinspect-root-p (phpinspect-meta-token existing-meta))
(phpinspect-taint-iterator-token-is-tainted-p taint-iterator existing-meta))))
(setq current-end-position (phpinspect-edtrack-current-position-at-point
edtrack (phpinspect-meta-end existing-meta)))
(setq token (phpinspect-meta-token existing-meta))
;;(message "reusing token %s" token)
;; Re-register existing token
(phpinspect-bmap-overlay
bmap previous-bmap existing-meta (- start-position original-position)
(phpinspect-pctx-consume-whitespace context))
;; Check if we can fast-forward to more siblings
(when (phpinspect-meta-right-siblings existing-meta)
(dolist (sibling (phpinspect-meta-right-siblings existing-meta))
(setq existing-meta (phpinspect-bmap-token-meta previous-bmap sibling))
(unless (phpinspect-taint-iterator-region-is-tainted-p
taint-iterator current-end-position (phpinspect-meta-end existing-meta))
(nconc tokens (list token))
(setq token (phpinspect-meta-token existing-meta))
(phpinspect-bmap-overlay
bmap previous-bmap existing-meta (- start-position original-position)
(phpinspect-pctx-consume-whitespace context))
(setq current-end-position (phpinspect-edtrack-current-position-at-point
edtrack (phpinspect-meta-end existing-meta))))))
;;(message "Current pos: %d, end pos: %d" (point) current-end-position)
(goto-char current-end-position)
;; Skip over whitespace after so that we don't do a full
;; run down all of the handlers during the next iteration
(when (looking-at (phpinspect-handler-regexp 'whitespace))
(funcall (phpinspect-handler 'whitespace) (match-string 0))))
,@(mapcar
(lambda (handler)
`((looking-at ,(plist-get (symbol-value handler) 'regexp))
(setq token (funcall ,(symbol-function handler) (match-string 0) max-point))
(when token
(phpinspect-pctx-register-token context token start-position (point)))))
handlers)
(t (forward-char)))
(when token
(nconc tokens (list token))
(setq token nil))))
(when root
(phpinspect-pctx-register-token context tokens root-start (point)))
;; Return
tokens)))))
(cl-defstruct (phpinspect-parser (:constructor phpinspect-make-parser))
(tree-keyword "root"
@ -450,7 +691,10 @@ parsed token. When the predicate returns a non-nil value, the parser stops
executing.")
(func nil
:type function
:documentation "The parser function."))
:documentation "The parser function.")
(incremental-func nil
:type function
:documentation "Incemental parser function"))
(cl-defmethod phpinspect-parser-compile ((parser phpinspect-parser))
"Create/return parser function."
@ -462,6 +706,16 @@ executing.")
(phpinspect-parser-handlers parser)
(phpinspect-parser-delimiter-predicate parser))))))
(cl-defmethod phpinspect-parser-compile-incremental ((parser phpinspect-parser))
"Like `phpinspect-parser-compile', but for an incremental version of the parser function."
(or (phpinspect-parser-incremental-func parser)
(setf (phpinspect-parser-incremental-func parser)
(byte-compile
(phpinspect-make-incremental-parser-function
(intern (concat ":" (phpinspect-parser-tree-keyword parser)))
(phpinspect-parser-handlers parser)
(phpinspect-parser-delimiter-predicate parser))))))
(defmacro phpinspect-defparser (name &rest parameters)
(declare (indent 1))
`(set (intern ,(symbol-name name) phpinspect-parser-obarray)
@ -567,18 +821,17 @@ executing.")
(while (not (or (= max-point (point)) (looking-at "\\*/")))
(forward-char))
(point)))
(comment-contents (buffer-substring region-start region-end))
(parser (phpinspect-get-parser-func 'doc-block))
(doc-block (with-temp-buffer
(insert comment-contents)
(goto-char (point-min))
(funcall parser (current-buffer) (point-max)))))
(doc-block (save-restriction
(goto-char region-start)
(narrow-to-region region-start region-end)
(funcall parser (current-buffer) (point-max) nil 'root))))
(forward-char 2)
doc-block))
(t
(let ((parser (phpinspect-get-parser-func 'comment))
(end-position (line-end-position)))
(funcall parser (current-buffer) end-position)))))
(funcall parser (current-buffer) end-position nil 'root)))))
(phpinspect-defhandler variable (start-token &rest _ignored)
"Handler for tokens indicating reference to a variable"
@ -590,7 +843,9 @@ executing.")
(phpinspect-defhandler whitespace (whitespace &rest _ignored)
"Handler that discards whitespace"
(regexp "[[:blank:]]+")
(regexp "[[:blank:]\n]+")
(when phpinspect-parse-context
(phpinspect-pctx-register-whitespace phpinspect-parse-context whitespace))
(forward-char (length whitespace)))
(phpinspect-defhandler equals (equals &rest _ignored)
@ -616,11 +871,11 @@ executing.")
(phpinspect-defhandler use-keyword (start-token max-point)
"Handler for the use keyword and tokens that might follow to give it meaning"
(regexp (concat "use" (phpinspect--word-end-regex)))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(forward-char (length start-token))
(let ((parser (phpinspect-get-parser-func 'use)))
(funcall parser (current-buffer) max-point)))
(funcall parser (current-buffer) max-point nil 'root)))
(phpinspect-defhandler attribute-reference (start-token &rest _ignored)
"Handler for references to object attributes, or static class attributes."
@ -648,7 +903,7 @@ executing.")
either a block has been parsed or another namespace keyword has
been encountered."
(regexp (concat "namespace" (phpinspect--word-end-regex)))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(forward-char (length start-token))
(funcall (phpinspect-get-parser-func 'namespace)
(current-buffer)
@ -663,17 +918,17 @@ executing.")
(phpinspect-defhandler const-keyword (start-token max-point)
"Handler for the const keyword."
(regexp (concat "const" (phpinspect--word-end-regex)))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(forward-char (length start-token))
(let* ((parser (phpinspect-get-parser-func 'const))
(token (funcall parser (current-buffer) max-point)))
(token (funcall parser (current-buffer) max-point nil 'root)))
(when (phpinspect-incomplete-token-p (car (last token)))
(setcar token :incomplete-const))
token))
(phpinspect-defhandler string (start-token &rest _ignored)
"Handler for strings"
(regexp "\"\\|'")
(regexp "\\(\"\\|'\\)")
(list :string (phpinspect--munch-string start-token)))
(phpinspect-defparser block-without-scopes
@ -692,7 +947,7 @@ static keywords with the same meaning as in a class block."
(continue-condition (lambda ()
(not (and (char-equal (char-after) ?})
(setq complete-block t)))))
(parsed (funcall parser (current-buffer) max-point continue-condition)))
(parsed (funcall parser (current-buffer) max-point continue-condition 'root)))
(if complete-block
(forward-char)
(setcar parsed :incomplete-block))
@ -714,7 +969,7 @@ static keywords with the same meaning as in a class block."
(continue-condition (lambda ()
(not (and (char-equal (char-after) ?})
(setq complete-block t)))))
(parsed (funcall parser (current-buffer) max-point continue-condition)))
(parsed (funcall parser (current-buffer) max-point continue-condition 'root)))
(if complete-block
(forward-char)
(setcar parsed :incomplete-block))
@ -819,11 +1074,14 @@ nature like argument lists"
(phpinspect-defhandler function-keyword (start-token max-point)
"Handler for the function keyword and tokens that follow to give it meaning"
(regexp (concat "function" (phpinspect--word-end-regex)))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(let* ((parser (phpinspect-get-or-create-declaration-parser))
(continue-condition (lambda () (not (char-equal (char-after) ?{))))
(declaration (funcall parser (current-buffer) max-point continue-condition)))
(if (phpinspect-end-of-token-p (car (last declaration)))
(continue-condition (lambda () (not (or (char-equal (char-after) ?{)
(char-equal (char-after) ?})))))
(declaration (funcall parser (current-buffer) max-point continue-condition 'root)))
(if (or (phpinspect-end-of-token-p (car (last declaration)))
(not (looking-at (phpinspect-handler-regexp 'block))))
(list :function declaration)
(list :function
declaration
@ -854,7 +1112,7 @@ nature like argument lists"
(concat word (phpinspect--word-end-regex)))
(list "public" "private" "protected")
"\\|"))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(forward-char (length start-token))
(funcall (phpinspect-get-parser-func
(cond ((string= start-token "public") 'scope-public)
@ -871,7 +1129,7 @@ nature like argument lists"
(phpinspect-defhandler static-keyword (start-token max-point)
"Handler for the static keyword"
(regexp (concat "static" (phpinspect--word-end-regex)))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(forward-char (length start-token))
(funcall (phpinspect-get-parser-func 'static)
(current-buffer)
@ -913,11 +1171,12 @@ nature like argument lists"
the properties of the class"
(regexp (concat "\\(abstract\\|final\\|class\\|interface\\|trait\\)"
(phpinspect--word-end-regex)))
(setq start-token (phpinspect--strip-last-char start-token))
(setq start-token (phpinspect--strip-word-end-space start-token))
(list :class (funcall (phpinspect-get-or-create-declaration-parser)
(current-buffer)
max-point
(lambda () (not (char-equal (char-after) ?{))))
(lambda () (not (char-equal (char-after) ?{)))
'root)
(funcall (phpinspect-handler 'class-block)
(char-to-string (char-after)) max-point)))
@ -936,8 +1195,7 @@ the properties of the class"
(re-search-forward "<\\?php\\|<\\?" nil t)
(funcall (phpinspect-get-parser-func 'root)
(current-buffer)
point))))
point nil 'root))))
(provide 'phpinspect-parser)
;;; phpinspect-parser.el ends here

@ -28,6 +28,40 @@
(require 'phpinspect-fs)
(require 'filenotify)
(defvar phpinspect-project-root-function #'phpinspect--find-project-root
"Function that phpinspect uses to find the root directory of a project.")
(defsubst phpinspect-current-project-root ()
"Call `phpinspect-project-root-function' with ARGS as arguments."
(unless (and (boundp 'phpinspect--buffer-project) phpinspect--buffer-project)
(set (make-local-variable 'phpinspect--buffer-project) (funcall phpinspect-project-root-function)))
phpinspect--buffer-project)
(defun phpinspect--find-project-root (&optional start-file)
"(Attempt to) Find the root directory of the visited PHP project.
If a found project root has a parent directory called \"vendor\",
the search continues upwards. See also
`phpinspect--locate-dominating-project-file'.
If START-FILE is provided, searching starts at the directory
level of START-FILE in stead of `default-directory`."
(let ((project-file (phpinspect--locate-dominating-project-file
(or start-file default-directory))))
(phpinspect--log "Checking for project root at %s" project-file)
(when project-file
(let* ((directory (file-name-directory project-file))
(directory-slugs (split-string (expand-file-name directory) "/")))
(if (not (member "vendor" directory-slugs))
(expand-file-name directory)
;; else. Only continue if the parent directory is not "/"
(let ((parent-without-vendor
(string-join (seq-take-while (lambda (s) (not (string= s "vendor" )))
directory-slugs)
"/")))
(when (not (or (string= parent-without-vendor "/")
(string= parent-without-vendor "")))
(phpinspect--find-project-root parent-without-vendor))))))))
(cl-defstruct (phpinspect-project (:constructor phpinspect--make-project))
(class-index (make-hash-table :test 'eq :size 100 :rehash-size 40)
:type hash-table

@ -0,0 +1,105 @@
(cl-defstruct (phpinspect-queue-item
(:constructor phpinspect-make-queue-item))
(next nil
:type phpinspect-queue-item
:documentation
"The next item in the queue")
(thing nil
:type any
:documentation
"The thing stored in the queue")
(previous nil
:type phpinspect-queue-item
:documentation
"The previous item in the queue")
(subscription nil
:type function
:read-only t
:documentation
"A function that should be called when items are
enqueued."))
(defsubst phpinspect-make-queue (&optional subscription)
(phpinspect-make-queue-item :subscription subscription))
;; Recursion causes max-eval-depth error here for long queues. Hence the loop
;; implementation for these two functions.
(cl-defmethod phpinspect-queue-last ((item phpinspect-queue-item))
"Get the last item in the queue that ITEM is part of."
(while (phpinspect-queue-item-next item)
(setq item (phpinspect-queue-item-next item)))
item)
(cl-defmethod phpinspect-queue-first ((item phpinspect-queue-item))
"Get the first item in the queue that ITEM is part of."
(while (phpinspect-queue-item-previous item)
(setq item (phpinspect-queue-item-previous item)))
item)
(cl-defmethod phpinspect-queue-enqueue ((item phpinspect-queue-item) thing)
"Add THING to the end of the queue that ITEM is part of."
(let ((last (phpinspect-queue-last item)))
(if (not (phpinspect-queue-item-thing last))
(setf (phpinspect-queue-item-thing last) thing)
(setf (phpinspect-queue-item-next last)
(phpinspect-make-queue-item
:previous last
:thing thing
:subscription (phpinspect-queue-item-subscription item)))))
(when (phpinspect-queue-item-subscription item)
(funcall (phpinspect-queue-item-subscription item))))
(cl-defmethod phpinspect-queue-dequeue ((item phpinspect-queue-item))
"Remove the thing at the front of the queue that ITEM is part of and return it."
(let* ((first (phpinspect-queue-first item))
(thing (phpinspect-queue-item-thing first))
(next (phpinspect-queue-item-next first)))
(when next (setf (phpinspect-queue-item-previous next) nil))
(cond ((and (eq item first) (not next))
(setf (phpinspect-queue-item-thing item)
nil))
((eq item first)
(setf (phpinspect-queue-item-thing item)
(phpinspect-queue-item-thing next))
(setf (phpinspect-queue-item-next item)
(phpinspect-queue-item-next next))))
thing))
(defmacro phpinspect-doqueue (place-and-queue &rest body)
"Loop over queue defined in PLACE-AND-QUEUE executing BODY.
PLACE-AND-QUEUE is a two-member list. The first item should be
the place that the current thing in the queue should be assigned
to upon each iteration. The second item should be a queue-item
belonging to the queue that must be iterated over.
BODY can be any form."
(declare (indent defun))
(let ((item-sym (gensym))
(place (car place-and-queue))
(queue (cadr place-and-queue)))
`(let* ((,item-sym (phpinspect-queue-first ,queue))
(,place (phpinspect-queue-item-thing ,item-sym)))
(when ,place
,@body
(while (setq ,item-sym (phpinspect-queue-item-next ,item-sym))
(setq ,place (phpinspect-queue-item-thing ,item-sym))
,@body)))))
(cl-defmethod phpinspect-queue-find
((item phpinspect-queue-item) thing comparison-func)
"Find THING in the queue that ITEM is part of using COMPARISON-FUNC."
(catch 'found
(phpinspect-doqueue (current-thing item)
(when (funcall comparison-func current-thing thing)
(throw 'found current-thing)))))
(cl-defmethod phpinspect-queue-enqueue-noduplicate
((item phpinspect-queue-item) thing comparison-func)
(when (not (phpinspect-queue-find item thing comparison-func))
(phpinspect-queue-enqueue item thing)))
(provide 'phpinspect-queue)

@ -0,0 +1,176 @@
;;; phpinspect-resolvecontext.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'phpinspect-bmap)
(require 'phpinspect-project)
(require 'phpinspect-parser)
(require 'phpinspect-type)
(cl-defstruct (phpinspect--resolvecontext
(:constructor phpinspect--make-resolvecontext))
(subject nil
:type phpinspect--token
:documentation
"The statement we're trying to resolve the type of.")
(project-root nil
:type string
:documentation
"The root directory of the project we're resolving types for.")
(enclosing-tokens nil
:type list
:documentation
"Tokens that enclose the subject."))
(cl-defmethod phpinspect--resolvecontext-push-enclosing-token
((resolvecontext phpinspect--resolvecontext) enclosing-token)
"Add ENCLOSING-TOKEN to RESOLVECONTEXTs enclosing token stack."
(push enclosing-token (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(defsubst phpinspect-blocklike-p (token)
(or (phpinspect-block-p token)
(phpinspect-function-p token)
(phpinspect-class-p token)
(phpinspect-namespace-p token)))
(defun phpinspect-find-statement-before-point (bmap meta point)
(phpinspect--log "going through %s" (phpinspect-meta-token meta))
(let ((children (reverse (cdr (phpinspect-meta-token meta)))))
(let ((previous-siblings))
(catch 'return
(dolist (child children)
(when (phpinspect-probably-token-p child)
(phpinspect--log "probably token: %s" child)
;; (phpinspect--log "Child: %s" child)
(setq child (phpinspect-bmap-token-meta bmap child))
(when (< (phpinspect-meta-start child) point)
(if (and (not previous-siblings) (phpinspect-blocklike-p (phpinspect-meta-token child)))
(progn
(phpinspect--log "recursing into %s" (phpinspect-meta-token child))
(throw 'return (phpinspect-find-statement-before-point bmap child point)))
(when (phpinspect-end-of-statement-p (phpinspect-meta-token child))
(phpinspect--log "returning %s, end of statement: %s" previous-siblings (phpinspect-meta-token child))
(throw 'return previous-siblings))
(push (phpinspect-meta-token child) previous-siblings)))))
previous-siblings))))
(cl-defmethod phpinspect-get-resolvecontext
((bmap phpinspect-bmap) (point integer))
(let* ((enclosing-tokens)
;; When there are no enclosing tokens, point is probably at the absolute
;; end of the buffer, so we find the last child before point.
(subject (phpinspect-bmap-last-token-before-point bmap point))
(subject-token)
(siblings))
;; Dig down through tokens that can contain statements
(catch 'break
(while (and subject (phpinspect-enclosing-token-p (phpinspect-meta-token subject)))
(phpinspect--log "Token %s is enclosing" (phpinspect-meta-token subject))
(let ((new-subject
(phpinspect-bmap-token-meta
bmap (car (last (phpinspect-meta-token subject))))))
(if new-subject
(setq subject new-subject)
(throw 'break nil)))))
(phpinspect--log "Initial resolvecontext subject token: %s"
(phpinspect-meta-token subject))
(when subject
(setq subject-token
(phpinspect-find-statement-before-point
bmap
(phpinspect-meta-parent subject)
point))
(phpinspect--log "Ultimate resolvecontext subject token: %s. Parent: %s"
subject-token (phpinspect-meta-token
(phpinspect-meta-parent subject)))
;; Iterate through subject parents to build stack of enclosing tokens
(let ((parent (phpinspect-meta-parent subject)))
(while parent
(let ((granny (phpinspect-meta-parent parent)))
(unless (and (phpinspect-block-p (phpinspect-meta-token parent))
(or (not granny)
(phpinspect-function-p (phpinspect-meta-token granny))
(phpinspect-class-p (phpinspect-meta-token granny))))
(push (phpinspect-meta-token parent) enclosing-tokens))
(setq parent (phpinspect-meta-parent parent))))))
(phpinspect--make-resolvecontext
:subject (phpinspect--get-last-statement-in-token subject-token)
:enclosing-tokens (nreverse enclosing-tokens)
:project-root (phpinspect-current-project-root))))
(defun phpinspect--get-resolvecontext (token &optional resolvecontext)
"Find the deepest nested incomplete token in TOKEN.
If RESOLVECONTEXT is nil, it is created. Returns RESOLVECONTEXT
of type `phpinspect--resolvecontext' containing the last
statement of the innermost incomplete token as subject
accompanied by all of its enclosing tokens."
(unless resolvecontext
(setq resolvecontext (phpinspect--make-resolvecontext
:project-root (phpinspect-current-project-root))))
(let ((last-token (car (last token)))
(last-encountered-token (car
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(unless (and (or (phpinspect-function-p last-encountered-token)
(phpinspect-class-p last-encountered-token))
(phpinspect-block-p token))
;; When a class or function has been inserted already, its block
;; doesn't need to be added on top.
(phpinspect--resolvecontext-push-enclosing-token resolvecontext token))
(if (phpinspect-incomplete-token-p last-token)
(phpinspect--get-resolvecontext last-token resolvecontext)
;; else
(setf (phpinspect--resolvecontext-subject resolvecontext)
(phpinspect--get-last-statement-in-token token))
resolvecontext)))
(defun phpinspect--make-type-resolver-for-resolvecontext
(resolvecontext)
(let ((namespace-or-root
(seq-find #'phpinspect-namespace-or-root-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(namespace-name))
(when (phpinspect-namespace-p namespace-or-root)
(setq namespace-name (cadadr namespace-or-root))
(setq namespace-or-root (phpinspect-namespace-body namespace-or-root)))
(phpinspect--make-type-resolver
(phpinspect--uses-to-types
(seq-filter #'phpinspect-use-p namespace-or-root))
(seq-find #'phpinspect-class-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext))
namespace-name)))
(provide 'phpinspect-resolvecontext)
;;; phpinspect-resolvecontext.el ends here

@ -87,10 +87,11 @@
(imports . ,,(append '(list)
(mapcar #'phpinspect--serialize-import
(alist-get 'imports index))))
(classes ,,@(mapcar (lambda (cons-class)
`(list ,(phpinspect--serialize-type (car cons-class))
,(phpinspect--serialize-indexed-class (cdr cons-class))))
(alist-get 'classes index)))
(classes . ,(list
,@(mapcar (lambda (cons-class)
`(cons ,(phpinspect--serialize-type (car cons-class))
,(phpinspect--serialize-indexed-class (cdr cons-class))))
(alist-get 'classes index))))
(functions . ,,(append '(list)
(mapcar #'phpinspect--serialize-function
(alist-get 'functions index))))))

@ -146,7 +146,16 @@ it evaluates to a non-nil value."
:code (append (phpinspect--pattern-code pattern1)
(phpinspect--pattern-code pattern2)))))
(defun phpinspect--locate-dominating-project-file (start-file)
"Locate the first dominating file in `phpinspect-project-root-file-list`.
Starts looking at START-FILE and then recurses up the directory
hierarchy as long as no matching files are found. See also
`locate-dominating-file'."
(let ((dominating-file))
(seq-find (lambda (file)
(setq dominating-file (locate-dominating-file start-file file)))
phpinspect-project-root-file-list)
dominating-file))
(provide 'phpinspect-util)
;;; phpinspect-util.el ends here

@ -27,127 +27,11 @@
(require 'phpinspect-project)
(require 'phpinspect-index)
(require 'phpinspect-class)
(require 'phpinspect-queue)
(defvar phpinspect-worker nil
"Contains the phpinspect worker that is used by all projects.")
(cl-defstruct (phpinspect-index-task
(:constructor phpinspect-make-index-task-generated))
"Represents an index task that can be executed by a `phpinspect-worker`."
(project nil
:type phpinspect-project
:documentation
"The project that the task should be executed for.")
(type nil
:type phpinspect--type
:documentation
"The type whose file should be indexed."))
(cl-defstruct (phpinspect-queue-item
(:constructor phpinspect-make-queue-item))
(next nil
:type phpinspect-queue-item
:documentation
"The next item in the queue")
(thing nil
:type any
:documentation
"The thing stored in the queue")
(previous nil
:type phpinspect-queue-item
:documentation
"The previous item in the queue")
(subscription nil
:type function
:read-only t
:documentation
"A function that should be called when items are
enqueued."))
(defsubst phpinspect-make-queue (&optional subscription)
(phpinspect-make-queue-item :subscription subscription))
;; Recursion causes max-eval-depth error here for long queues. Hence the loop
;; implementation for these two functions.
(cl-defmethod phpinspect-queue-last ((item phpinspect-queue-item))
"Get the last item in the queue that ITEM is part of."
(while (phpinspect-queue-item-next item)
(setq item (phpinspect-queue-item-next item)))
item)
(cl-defmethod phpinspect-queue-first ((item phpinspect-queue-item))
"Get the first item in the queue that ITEM is part of."
(while (phpinspect-queue-item-previous item)
(setq item (phpinspect-queue-item-previous item)))
item)
(cl-defmethod phpinspect-queue-enqueue ((item phpinspect-queue-item) thing)
"Add THING to the end of the queue that ITEM is part of."
(let ((last (phpinspect-queue-last item)))
(if (not (phpinspect-queue-item-thing last))
(setf (phpinspect-queue-item-thing last) thing)
(setf (phpinspect-queue-item-next last)
(phpinspect-make-queue-item
:previous last
:thing thing
:subscription (phpinspect-queue-item-subscription item)))))
(when (phpinspect-queue-item-subscription item)
(funcall (phpinspect-queue-item-subscription item))))
(cl-defmethod phpinspect-queue-dequeue ((item phpinspect-queue-item))
"Remove the thing at the front of the queue that ITEM is part of an return it."
(let* ((first (phpinspect-queue-first item))
(thing (phpinspect-queue-item-thing first))
(next (phpinspect-queue-item-next first)))
(when next (setf (phpinspect-queue-item-previous next) nil))
(cond ((and (eq item first) (not next))
(setf (phpinspect-queue-item-thing item)
nil))
((eq item first)
(setf (phpinspect-queue-item-thing item)
(phpinspect-queue-item-thing next))
(setf (phpinspect-queue-item-next item)
(phpinspect-queue-item-next next))))
thing))
(defmacro phpinspect-doqueue (place-and-queue &rest body)
"Loop over queue defined in PLACE-AND-QUEUE executing BODY.
PLACE-AND-QUEUE is a two-member list. The first item should be
the place that the current thing in the queue should be assigned
to upon each iteration. The second item should be a queue-item
belonging to the queue that must be iterated over.
BODY can be any form."
(declare (indent defun))
(let ((item-sym (gensym))
(place (car place-and-queue))
(queue (cadr place-and-queue)))
`(let* ((,item-sym (phpinspect-queue-first ,queue))
(,place (phpinspect-queue-item-thing ,item-sym)))
(when ,place
,@body
(while (setq ,item-sym (phpinspect-queue-item-next ,item-sym))
(setq ,place (phpinspect-queue-item-thing ,item-sym))
,@body)))))
(cl-defmethod phpinspect-queue-find
((item phpinspect-queue-item) thing comparison-func)
"Find THING in the queue that ITEM is part of using COMPARISON-FUNC."
(catch 'found
(phpinspect-doqueue (current-thing item)
(when (funcall comparison-func current-thing thing)
(throw 'found current-thing)))))
(cl-defmethod phpinspect-queue-enqueue-noduplicate
((item phpinspect-queue-item) thing comparison-func)
(when (not (phpinspect-queue-find item thing comparison-func))
(phpinspect-queue-enqueue item thing)))
(cl-defmethod phpinspect-queue-await-insert ((item phpinspect-queue-item))
(condition-wait (phpinspect-queue-item-insert item)))
(cl-defstruct (phpinspect-worker
(:constructor phpinspect-make-worker-generated))
(queue nil
@ -223,19 +107,10 @@ on the worker independent of dynamic variables during testing.")
"Enqueue a TASK to be executed by WORKER.")
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker) task)
(phpinspect-queue-enqueue (phpinspect-worker-queue worker) task))
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker)
(task phpinspect-index-task))
"Specialized enqueuement method for index tasks. Prevents
indexation tasks from being added when there are identical tasks
already present in the queue."
(phpinspect-queue-enqueue-noduplicate (phpinspect-worker-queue worker) task #'phpinspect-index-task=))
(cl-defmethod phpinspect-index-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
(phpinspect--type= (phpinspect-index-task-type task1) (phpinspect-index-task-type task2))))
(phpinspect-queue-enqueue-noduplicate (phpinspect-worker-queue worker) task #'phpinspect-task=))
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-dynamic-worker) task)
(phpinspect-worker-enqueue (phpinspect-resolve-dynamic-worker worker)
@ -328,20 +203,57 @@ CONTINUE must be a condition-variable"
(interactive)
(phpinspect-worker-stop phpinspect-worker))
;;; TASKS
;; The rest of this file contains task definitions. Tasks represent actions that
;; can be executed by `phpinspect-worker'. Some methods are required to be
;; implemented for all tasks, while others aren't.
;; REQUIRED METHODS:
;; - phpinspect-task-execute
;; - phpinspect-task-project
;; OPTIONAL METHODS:
;; - phpinspect-task=
;;; Code:
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
(cl-defmethod phpinspect-task= (task1 task2)
"Whether or not TASK1 and TASK2 are set to execute the exact same action."
nil)
(cl-defgeneric phpinspect-task-project (task)
"The project that this task belongs to.")
;;; INDEX TASK
(cl-defstruct (phpinspect-index-task
(:constructor phpinspect-make-index-task-generated))
"Represents an index task that can be executed by a `phpinspect-worker`."
(project nil
:type phpinspect-project
:documentation
"The project that the task should be executed for.")
(type nil
:type phpinspect--type
:documentation
"The type whose file should be indexed."))
(cl-defgeneric phpinspect-make-index-task ((project phpinspect-project)
(type phpinspect--type))
(phpinspect-make-index-task-generated
:project project
:type type))
(cl-defgeneric phpinspect-task-project (task)
"The project that this task belongs to.")
(cl-defmethod phpinspect-task-project ((task phpinspect-index-task))
(phpinspect-index-task-project task))
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
(cl-defmethod phpinspect-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
(phpinspect--type= (phpinspect-index-task-type task1) (phpinspect-index-task-type task2))))
(cl-defmethod phpinspect-task-execute ((task phpinspect-index-task)
(worker phpinspect-worker))
@ -349,12 +261,12 @@ CONTINUE must be a condition-variable"
(let ((project (phpinspect-index-task-project task))
(is-native-type (phpinspect--type-is-native
(phpinspect-index-task-type task))))
(phpinspect--log "Indexing class %s for project in %s from index thread"
(phpinspect--log "Indexing class %s for project in %s as task."
(phpinspect-index-task-type task)
(phpinspect-project-root project))
(cond (is-native-type
(phpinspect--log "Skipping indexation of native type %s"
(phpinspect--log "Skipping indexation of native type %s as task"
(phpinspect-index-task-type task))
;; We can skip pausing when a native type is encountered
@ -367,6 +279,7 @@ CONTINUE must be a condition-variable"
(when root-index
(phpinspect-project-add-index project root-index)))))))
;;; PARSE BUFFER TASK
(provide 'phpinspect-worker)
;;; phpinspect-worker.el ends here

@ -41,6 +41,7 @@
(require 'phpinspect-autoload)
(require 'phpinspect-imports)
(require 'phpinspect-buffer)
(require 'phpinspect-resolvecontext)
(defvar phpinspect-auto-reindex nil
"Whether or not phpinspect should automatically search for new
@ -58,9 +59,6 @@ phpinspect")
(defvar phpinspect-insert-file-contents-function #'insert-file-contents-literally
"Function that phpinspect uses to insert file contents into a buffer.")
(defvar phpinspect-project-root-function #'phpinspect--find-project-root
"Function that phpinspect uses to find the root directory of a project.")
(defvar phpinspect-type-filepath-function #'phpinspect-get-class-filepath
"Function that phpinspect uses to find the filepath of a class by its FQN.")
@ -107,56 +105,6 @@ candidate. Candidates can be indexed functions and variables.")
(phpinspect--function-return-type completion-candidate)))
:kind 'function))
(cl-defstruct (phpinspect--resolvecontext
(:constructor phpinspect--make-resolvecontext))
(subject nil
:type phpinspect--token
:documentation
"The statement we're trying to resolve the type of.")
(project-root nil
:type string
:documentation
"The root directory of the project we're resolving types for.")
(enclosing-tokens nil
:type list
:documentation
"Tokens that enclose the subject."))
(cl-defmethod phpinspect--resolvecontext-push-enclosing-token
((resolvecontext phpinspect--resolvecontext) enclosing-token)
"Add ENCLOSING-TOKEN to RESOLVECONTEXTs enclosing token stack."
(push enclosing-token (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(defun phpinspect--get-resolvecontext (token &optional resolvecontext)
"Find the deepest nested incomplete token in TOKEN.
If RESOLVECONTEXT is nil, it is created. Returns RESOLVECONTEXT
of type `phpinspect--resolvecontext' containing the last
statement of the innermost incomplete token as subject
accompanied by all of its enclosing tokens."
(unless resolvecontext
(setq resolvecontext (phpinspect--make-resolvecontext
:project-root (phpinspect-current-project-root))))
(let ((last-token (car (last token)))
(last-encountered-token (car
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(unless (and (or (phpinspect-function-p last-encountered-token)
(phpinspect-class-p last-encountered-token))
(phpinspect-block-p token))
;; When a class or function has been inserted already, its block
;; doesn't need to be added on top.
(phpinspect--resolvecontext-push-enclosing-token resolvecontext token))
(if (phpinspect-incomplete-token-p last-token)
(phpinspect--get-resolvecontext last-token resolvecontext)
;; else
(setf (phpinspect--resolvecontext-subject resolvecontext)
(phpinspect--get-last-statement-in-token token))
resolvecontext)))
(defsubst phpinspect-cache-project-class (project-root indexed-class)
(when project-root
@ -273,6 +221,16 @@ accompanied by all of its enclosing tokens."
(current-buffer)
(point-max)))
(defun phpinspect-parse-string-to-bmap (string)
(with-temp-buffer
(insert string)
(let ((context (phpinspect-make-pctx :incremental t
:bmap (phpinspect-make-bmap))))
(phpinspect-with-parse-context context
(phpinspect-parse-current-buffer))
(phpinspect-pctx-bmap context))))
(defun phpinspect-parse-string (string)
(with-temp-buffer
(insert string)
@ -324,22 +282,32 @@ TODO:
- Respect `eldoc-echo-area-use-multiline-p`
- This function is too big and has repetitive code. Split up and simplify.
"
(phpinspect--log "Starting eldoc function execution")
(let* ((token-tree (phpinspect-parse-buffer-until-point (current-buffer) (point)))
(resolvecontext (phpinspect--get-resolvecontext token-tree))
(incomplete-token (car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(let* ((token-map (phpinspect-buffer-parse-map phpinspect-current-buffer))
(resolvecontext (phpinspect-get-resolvecontext token-map (point)))
(parent-token (car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(enclosing-token (cadr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(statement (phpinspect--get-last-statement-in-token
enclosing-token))
(statement (phpinspect--resolvecontext-subject resolvecontext))
(arg-list)
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext))
(static))
(phpinspect--log "Eldoc statement before checking outside list: %s" statement)
(when (and (phpinspect-list-p parent-token) enclosing-token)
(setq statement
(phpinspect-find-statement-before-point
token-map (phpinspect-bmap-token-meta token-map enclosing-token)
(phpinspect-meta-end
(phpinspect-bmap-token-meta token-map parent-token)))))
(phpinspect--log "Enclosing token: %s" enclosing-token)
(phpinspect--log "reference token: %s" (car (last statement 2)))
(phpinspect--log "Eldoc statement: %s" statement)
(when (and (phpinspect-incomplete-list-p incomplete-token)
(setq arg-list (seq-find #'phpinspect-list-p (reverse statement)))
(when (and (phpinspect-list-p arg-list)
enclosing-token
(or (phpinspect-object-attrib-p (car (last statement 2)))
(setq static (phpinspect-static-attrib-p (car (last statement 2))))))
@ -370,7 +338,7 @@ TODO:
(when method
(let ((arg-count -1)
(comma-count
(length (seq-filter #'phpinspect-comma-p incomplete-token))))
(length (seq-filter #'phpinspect-comma-p arg-list))))
(concat (truncate-string-to-width
(phpinspect--function-name method) phpinspect-eldoc-word-width) ": ("
(mapconcat
@ -794,10 +762,14 @@ more recent"
(phpinspect--log "Failed to find methods for class %s :(" class))
methods))
(defun phpinspect-after-change-function (start end pre-change-length)
(when phpinspect-current-buffer
(phpinspect-buffer-register-edit phpinspect-current-buffer start end pre-change-length)))
(defun phpinspect--init-mode ()
"Initialize the phpinspect minor mode for the current buffer."
(setq phpinspect-current-buffer (phpinspect-make-buffer :buffer (current-buffer)))
(add-hook 'after-change-functions #'phpinspect-after-change-function)
(make-local-variable 'company-backends)
(add-to-list 'company-backends #'phpinspect-company-backend)
@ -824,7 +796,9 @@ Assuming that files are only changed from within Emacs, this
keeps the cache valid. If changes are made outside of Emacs,
users will have to use \\[phpinspect-purge-cache]."
(when (and (boundp 'phpinspect-mode) phpinspect-mode)
(setq phpinspect--buffer-index (phpinspect-index-current-buffer))
(setq phpinspect--buffer-index
(phpinspect--index-tokens
(phpinspect-buffer-reparse phpinspect-current-buffer)))
(let ((imports (alist-get 'imports phpinspect--buffer-index))
(project (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
@ -997,7 +971,7 @@ level of a token. Nested variables are ignored."
strings))
(defun phpinspect--suggest-attributes-at-point
(token-tree resolvecontext &optional static)
(resolvecontext &optional static)
"Suggest object or class attributes at point.
TOKEN-TREE must be a syntax tree containing enough context to
@ -1028,25 +1002,6 @@ static variables and static methods."
static)
(funcall method-lister type)))))))
(defun phpinspect--make-type-resolver-for-resolvecontext
(resolvecontext)
(let ((namespace-or-root
(seq-find #'phpinspect-namespace-or-root-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(namespace-name))
(when (phpinspect-namespace-p namespace-or-root)
(setq namespace-name (cadadr namespace-or-root))
(setq namespace-or-root (phpinspect-namespace-body namespace-or-root)))
(phpinspect--make-type-resolver
(phpinspect--uses-to-types
(seq-filter #'phpinspect-use-p namespace-or-root))
(seq-find #'phpinspect-class-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext))
namespace-name)))
(defun phpinspect--get-last-statement-in-token (token)
(setq token (cond ((phpinspect-function-p token)
(phpinspect-function-block token))
@ -1097,9 +1052,9 @@ static variables and static methods."
(seq-filter #'phpinspect--variable-name variables)))
(defun phpinspect--suggest-at-point ()
(phpinspect--log "Entering suggest at point." )
(let* ((token-tree (phpinspect-parse-buffer-until-point (current-buffer) (point)))
(resolvecontext (phpinspect--get-resolvecontext token-tree))
(phpinspect--log "Entering suggest at point. Point: %d" (point))
(let* ((bmap (phpinspect-buffer-parse-map phpinspect-current-buffer))
(resolvecontext (phpinspect-get-resolvecontext bmap (point)))
(last-tokens (last (phpinspect--resolvecontext-subject resolvecontext) 2)))
(phpinspect--log "Subject: %s" (phpinspect--resolvecontext-subject
resolvecontext))
@ -1107,11 +1062,10 @@ static variables and static methods."
(cond ((and (phpinspect-object-attrib-p (car last-tokens))
(phpinspect-word-p (cadr last-tokens)))
(phpinspect--log "word-attributes")
(phpinspect--suggest-attributes-at-point token-tree
resolvecontext))
(phpinspect--suggest-attributes-at-point resolvecontext))
((phpinspect-object-attrib-p (cadr last-tokens))
(phpinspect--log "object-attributes")
(phpinspect--suggest-attributes-at-point token-tree resolvecontext))
(phpinspect--suggest-attributes-at-point resolvecontext))
((phpinspect-static-attrib-p (cadr last-tokens))
(phpinspect--log "static-attributes")
(phpinspect--suggest-attributes-at-point token-tree resolvecontext t))
@ -1193,48 +1147,6 @@ currently opened projects."
;; Assign a fresh cache object
(setq phpinspect-cache (phpinspect--make-cache)))
(defun phpinspect--locate-dominating-project-file (start-file)
"Locate the first dominating file in `phpinspect-project-root-file-list`.
Starts looking at START-FILE and then recurses up the directory
hierarchy as long as no matching files are found. See also
`locate-dominating-file'."
(let ((dominating-file))
(seq-find (lambda (file)
(setq dominating-file (locate-dominating-file start-file file)))
phpinspect-project-root-file-list)
dominating-file))
(defun phpinspect--find-project-root (&optional start-file)
"(Attempt to) Find the root directory of the visited PHP project.
If a found project root has a parent directory called \"vendor\",
the search continues upwards. See also
`phpinspect--locate-dominating-project-file'.
If START-FILE is provided, searching starts at the directory
level of START-FILE in stead of `default-directory`."
(let ((project-file (phpinspect--locate-dominating-project-file
(or start-file default-directory))))
(phpinspect--log "Checking for project root at %s" project-file)
(when project-file
(let* ((directory (file-name-directory project-file))
(directory-slugs (split-string (expand-file-name directory) "/")))
(if (not (member "vendor" directory-slugs))
(expand-file-name directory)
;; else. Only continue if the parent directory is not "/"
(let ((parent-without-vendor
(string-join (seq-take-while (lambda (s) (not (string= s "vendor" )))
directory-slugs)
"/")))
(when (not (or (string= parent-without-vendor "/")
(string= parent-without-vendor "")))
(phpinspect--find-project-root parent-without-vendor))))))))
(defsubst phpinspect-current-project-root ()
"Call `phpinspect-project-root-function' with ARGS as arguments."
(unless (and (boundp 'phpinspect--buffer-project) phpinspect--buffer-project)
(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."

@ -1 +1 @@
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Controller") (:incomplete-block (:use (:word "Symfony\\Component\\HttpFoundation\\Response") (:terminator ";")) (:use (:word "App\\Entity\\Address") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\RedirectResponse") (:terminator ";")) (:use (:word "App\\Repository\\AddressRepository") (:terminator ";")) (:use (:word "App\\Repository\\UserRepository") (:terminator ";")) (:use (:word "Doctrine\\ORM\\EntityManagerInterface") (:terminator ";")) (:use (:word "Twig\\Environment") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\Request") (:terminator ";")) (:use (:word "Symfony\\Component\\Routing\\Annotation\\Route") (:terminator ";")) (:word "class") (:word "AddressController") (:incomplete-block (:const (:word "A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE") (:assignment "=") (:string "a value") (:terminator ";")) (:public (:const (:word "ARRAY_CONSTANT") (:assignment "=") (:array (:string "key") (:fat-arrow "=>") (:string "value") (:comma ",") (:string "key") (:fat-arrow "=>")) (:terminator ";"))) (:private (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil)))))))))
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Controller") (:incomplete-block (:use (:word "Symfony\\Component\\HttpFoundation\\Response") (:terminator ";")) (:use (:word "App\\Entity\\Address") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\RedirectResponse") (:terminator ";")) (:use (:word "App\\Repository\\AddressRepository") (:terminator ";")) (:use (:word "App\\Repository\\UserRepository") (:terminator ";")) (:use (:word "Doctrine\\ORM\\EntityManagerInterface") (:terminator ";")) (:use (:word "Twig\\Environment") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\Request") (:terminator ";")) (:use (:word "Symfony\\Component\\Routing\\Annotation\\Route") (:terminator ";")) (:class (:declaration (:word "class") (:word "AddressController")) (:incomplete-block (:const (:word "A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE") (:assignment "=") (:string "a value") (:terminator ";")) (:public (:const (:word "ARRAY_CONSTANT") (:assignment "=") (:array (:string "key") (:fat-arrow "=>") (:string "value") (:comma ",") (:string "key") (:fat-arrow "=>")) (:terminator ";"))) (:private (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil))))))))))

File diff suppressed because one or more lines are too long

@ -1 +1 @@
`(phpinspect--root-index (imports \, (list)) (classes ,(list (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t) `(phpinspect--indexed-class (class-name \, (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t)) (imports \, (list (cons (phpinspect-intern-name "ORM") (phpinspect--make-type :name "\\Doctrine\\ORM\\Mapping" :collection nil :contains nil :fully-qualified t)))) (methods \, (list (phpinspect--make-function :name "arrayReturn" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\array" :collection t :contains (phpinspect--make-type :name "\\App\\Entity\\DateTime" :collection nil :contains nil :fully-qualified t) :fully-qualified t)) (phpinspect--make-function :name "getCreationTime" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "isValid" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "hasStudentRole" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getUser" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getToken" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "__construct" :scope '(:public) :arguments (list (list "token" (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (list "user" (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (list "valid" (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (list "creation_time" (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t))) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)))) (static-methods \, (list)) (static-variables \, (list)) (variables \, (list (phpinspect--make-variable :name "creation_time" :type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "valid" :type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "user" :type (phpinspect--make-type :name "\\App\\Entity\\App\\\\Entity\\\\User" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "token" :type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t) :scope '(:private)))) (constants \, (list)) (extends \, (list)) (implements \, (list))))) (functions \, (list)))
`(phpinspect--root-index (imports \, (list)) (classes \, (list (cons (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t) `(phpinspect--indexed-class (class-name \, (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t)) (imports \, (list (cons (phpinspect-intern-name "ORM") (phpinspect--make-type :name "\\Doctrine\\ORM\\Mapping" :collection nil :contains nil :fully-qualified t)))) (methods \, (list (phpinspect--make-function :name "arrayReturn" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\array" :collection t :contains (phpinspect--make-type :name "\\App\\Entity\\DateTime" :collection nil :contains nil :fully-qualified t) :fully-qualified t)) (phpinspect--make-function :name "getCreationTime" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "isValid" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "hasStudentRole" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getUser" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getToken" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "__construct" :scope '(:public) :arguments (list (list "token" (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (list "user" (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (list "valid" (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (list "creation_time" (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t))) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)))) (static-methods \, (list)) (static-variables \, (list)) (variables \, (list (phpinspect--make-variable :name "creation_time" :type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "valid" :type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "user" :type (phpinspect--make-type :name "\\App\\Entity\\App\\\\Entity\\\\User" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "token" :type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t) :scope '(:private)))) (constants \, (list)) (extends \, (list)) (implements \, (list)))))) (functions \, (list)))

@ -1 +1 @@
`(phpinspect--root-index (imports \, (list)) (classes ,(list (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t) `(phpinspect--indexed-class (class-name \, (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t)) (imports \, (list (cons (phpinspect-intern-name "ORM") (phpinspect--make-type :name "\\Doctrine\\ORM\\Mapping" :collection nil :contains nil :fully-qualified t)))) (methods \, (list (phpinspect--make-function :name "getCreationTime" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "anAddedFunction" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "isValid" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "hasStudentRole" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getUser" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getToken" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "__construct" :scope '(:public) :arguments (list (list "token" (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (list "user" (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (list "valid" (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (list "creation_time" (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t))) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)))) (static-methods \, (list)) (static-variables \, (list)) (variables \, (list (phpinspect--make-variable :name "creation_time" :type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "valid" :type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "user" :type (phpinspect--make-type :name "\\App\\Entity\\App\\\\Entity\\\\User" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "extra" :type nil :scope '(:private)) (phpinspect--make-variable :name "token" :type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t) :scope '(:private)))) (constants \, (list)) (extends \, (list)) (implements \, (list))))) (functions \, (list)))
`(phpinspect--root-index (imports \, (list)) (classes \, (list (cons (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t) `(phpinspect--indexed-class (class-name \, (phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil :contains nil :fully-qualified t)) (imports \, (list (cons (phpinspect-intern-name "ORM") (phpinspect--make-type :name "\\Doctrine\\ORM\\Mapping" :collection nil :contains nil :fully-qualified t)))) (methods \, (list (phpinspect--make-function :name "getCreationTime" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "anAddedFunction" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "isValid" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "hasStudentRole" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getUser" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getToken" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "__construct" :scope '(:public) :arguments (list (list "token" (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (list "user" (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (list "valid" (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (list "creation_time" (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t))) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)))) (static-methods \, (list)) (static-variables \, (list)) (variables \, (list (phpinspect--make-variable :name "creation_time" :type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "valid" :type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "user" :type (phpinspect--make-type :name "\\App\\Entity\\App\\\\Entity\\\\User" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "extra" :type nil :scope '(:private)) (phpinspect--make-variable :name "token" :type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t) :scope '(:private)))) (constants \, (list)) (extends \, (list)) (implements \, (list)))))) (functions \, (list)))

@ -62,8 +62,11 @@
(concat phpinspect-test-php-file-directory "/" name ".php")))
(ert-deftest phpinspect-get-variable-type-in-block ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo;"))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo; Whatever comes after don't matter.")
(bmap (phpinspect-parse-string-to-bmap code))
(tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(bmap-context (phpinspect-get-resolvecontext bmap (- (length code) 36)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -78,13 +81,22 @@
context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(phpinspect--make-type-resolver-for-resolvecontext context)))
(bmap-result (phpinspect-get-variable-type-in-block
bmap-context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
result))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
bmap-result)))))
(ert-deftest phpinspect-get-pattern-type-in-block ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;")
(bmap (phpinspect-parse-string-to-bmap "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;"))
(context (phpinspect-get-resolvecontext bmap (length code)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -104,9 +116,25 @@
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
(ert-deftest phpinspect-get-resolvecontext-multi-strategy ()
(let* ((code1 "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]; }}")
(code2 "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]")
(bmap (phpinspect-parse-string-to-bmap code1))
(tokens (phpinspect-parse-string code2))
(context1 (phpinspect-get-resolvecontext bmap (- (length code1) 4)))
(context2 (phpinspect--get-resolvecontext tokens)))
(should (equal (phpinspect--resolvecontext-subject context1)
(phpinspect--resolvecontext-subject context2)))
(should (= (length (phpinspect--resolvecontext-enclosing-tokens context1))
(length (phpinspect--resolvecontext-enclosing-tokens context2))))))
(ert-deftest phpinspect-get-variable-type-in-block-array-access ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]; }}")
(tokens (phpinspect-parse-string-to-bmap code))
(context (phpinspect-get-resolvecontext tokens (- (length code) 4)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -136,8 +164,9 @@
(ert-deftest phpinspect-get-variable-type-in-block-array-foreach ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; foreach ($foo as $bar) {$bar->"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; foreach ($foo as $bar) {$bar->")
(bmap (phpinspect-parse-string-to-bmap code))
(context (phpinspect-get-resolvecontext bmap (length code)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -161,8 +190,9 @@
(ert-deftest phpinspect-get-variable-type-in-block-nested-array ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = [[$baz]]; foreach ($foo[0] as $bar) {$bar->"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = [[$baz]]; foreach ($foo[0] as $bar) {$bar->")
(bmap (phpinspect-parse-string-to-bmap code))
(context (phpinspect-get-resolvecontext bmap (length code)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -286,120 +316,10 @@
(phpinspect-test-read-fixture-data "IndexClass1"))))
(should (equal index expected-result))))
(ert-deftest phpinspect-index-tokens-class ()
(let* ((index1
(phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1")))
(index2
(phpinspect-test-read-fixture-serialization "IndexClass1-indexed"))
(index1-class (cdr (alist-get 'classes index1)))
(index2-class (cdr (alist-get 'classes index2))))
(dolist (key '(class-name imports methods static-methods static-variables variables constants extends implements))
(should (equal (alist-get key index1-class)
(alist-get key index2-class))))))
(ert-deftest phpinspect-get-resolvecontext ()
(let ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data "IncompleteClass"))))
(should (equal (phpinspect--resolvecontext-subject resolvecontext)
'((:variable "this")
(:object-attrib (:word "em"))
(:object-attrib nil))))
(should (phpinspect-root-p
(car (last (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))))
(should (phpinspect-incomplete-list-p
(car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(should (phpinspect-incomplete-function-p
(cadr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(should (phpinspect-incomplete-class-p
(cadddr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext ()
(let* ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data "IncompleteClass")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver
(phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver
(phpinspect--make-type :name "GastonLagaffe"))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver
(phpinspect--make-type :name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext-namespace-block ()
(let* ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data
"IncompleteClassBlockedNamespace")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver (phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver (phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver (phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type
:name "GastonLagaffe"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type :name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext-multiple-namespace-blocks ()
(let* ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data
"IncompleteClassMultipleNamespaces")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver
(phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type :name "GastonLagaffe"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type
:name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-resolve-type-from-context ()
(let* ((token-tree (phpinspect-parse-string "
(let* ((pctx (phpinspect-make-pctx :incremental t))
(code "
namespace Amazing;
class FluffBall
@ -416,8 +336,18 @@ class FluffBall
$ball = $this->fluffer;
if ($ball) {
if(isset($ball->fluff()->poof->upFluff->"))
(fluffer (phpinspect-parse-string "
if(isset($ball->fluff()->poof->upFluff->)) {
$this->beFluffy();
}
}
$ball->fluff()->poof->
}
}")
(token-tree (phpinspect-with-parse-context pctx
(phpinspect-parse-string code)))
(bmap (phpinspect-pctx-bmap pctx))
(fluffer (phpinspect-parse-string "
namespace Amazing;
use Vendor\\FluffLib\\Fluff;
@ -448,7 +378,7 @@ class FlufferUpper
}
}"))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(context (phpinspect--get-resolvecontext token-tree)))
(context (phpinspect-get-resolvecontext bmap 310)))
(setf (phpinspect--resolvecontext-project-root context)
"phpinspect-test")
@ -461,6 +391,14 @@ class FlufferUpper
(should (phpinspect--type=
(phpinspect--make-type :name "\\Vendor\\FluffLib\\DoubleFluffer")
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))
(setq context (phpinspect-get-resolvecontext bmap 405))
(should (phpinspect--type=
(phpinspect--make-type :name "\\Vendor\\FluffLib\\FlufferUpper")
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
@ -477,7 +415,7 @@ class Thing
function doStuff()
{
$this->getThis(")
$this->getThis(new \DateTime(), bla)")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
@ -490,6 +428,10 @@ class Thing
(should (string= "getThis: ($moment DateTime, $thing Thing, $other): Thing"
(with-temp-buffer
(insert php-code)
(backward-char)
(setq-local phpinspect-current-buffer
(phpinspect-make-buffer :buffer (current-buffer)))
(phpinspect-buffer-parse phpinspect-current-buffer)
(phpinspect-eldoc-function))))))
(ert-deftest phpinspect-eldoc-function-for-static-method ()
@ -516,11 +458,14 @@ class Thing
(should (string= "doThing: ($moment DateTime, $thing Thing, $other): Thing"
(with-temp-buffer
(insert php-code)
(setq-local phpinspect-current-buffer
(phpinspect-make-buffer :buffer (current-buffer)))
(phpinspect-eldoc-function))))))
(ert-deftest phpinspect-resolve-type-from-context-static-method ()
(let* ((php-code "
(with-temp-buffer
(insert "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
@ -531,11 +476,14 @@ class Thing
function doStuff()
{
self::doThing()->")
(tokens (phpinspect-parse-string php-code))
(let* ((bmap (phpinspect-make-bmap))
(tokens (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap bmap)
(phpinspect-parse-current-buffer)))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(context (phpinspect--get-resolvecontext tokens)))
(context (phpinspect-get-resolvecontext bmap (point))))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
@ -545,10 +493,11 @@ class Thing
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))))
context)))))))
(ert-deftest phpinspect-resolve-type-from-context-static-method-with-preceding-words ()
(let* ((php-code "
(with-temp-buffer
(insert "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
@ -560,21 +509,24 @@ class Thing
{
if (true) {
return self::doThing()->")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(context (phpinspect--get-resolvecontext tokens)))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(let* ((bmap (phpinspect-make-bmap))
(tokens (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap bmap)
(phpinspect-parse-current-buffer)))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(context (phpinspect-get-resolvecontext bmap (point))))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context)))))))
(ert-deftest phpinspect-get-last-statement-in-token-with-static-attribute-context ()
(let* ((php-code-function "
@ -634,6 +586,16 @@ class Thing
(should (equal '((:variable "wat") (:object-attrib "call"))
(phpinspect--assignment-from (car result))))))
(ert-deftest phpinspect-parse-function-missing-open-block ()
(let ((parsed (phpinspect-parse-string "function bla() echo 'Hello'}")))
(should (equal '(:root (:function
(:declaration (:word "function") (:word "bla") (:list)
(:word "echo") (:word "Hello"))))
parsed))))
(ert-deftest phpinspect-parse-string-token ()
(let ((parsed (phpinspect-parse-string "<?php 'string'")))
(should (equal '(:root (:string "string")) parsed))))
(load-file (concat phpinspect-test-directory "/test-worker.el"))
(load-file (concat phpinspect-test-directory "/test-autoload.el"))
@ -644,6 +606,9 @@ class Thing
(load-file (concat phpinspect-test-directory "/test-class.el"))
(load-file (concat phpinspect-test-directory "/test-type.el"))
(load-file (concat phpinspect-test-directory "/test-util.el"))
(load-file (concat phpinspect-test-directory "/test-bmap.el"))
(load-file (concat phpinspect-test-directory "/test-edtrack.el"))
(load-file (concat phpinspect-test-directory "/test-resolvecontext.el"))
(provide 'phpinspect-test)
;;; phpinspect-test.el ends here

@ -0,0 +1,102 @@
(require 'phpinspect-bmap)
(ert-deftest phpinspect-bmap-overlay ()
(let ((bmap (phpinspect-make-bmap))
(bmap2 (phpinspect-make-bmap))
(bmap3 (phpinspect-make-bmap)))
(phpinspect-bmap-register bmap 10 20 'token)
(phpinspect-bmap-register bmap2 20 24 'token2)
(phpinspect-bmap-register bmap3 40 50 'token3)
(should (phpinspect-bmap-token-starting-at bmap 10))
(phpinspect-bmap-overlay
bmap bmap3 (phpinspect-bmap-token-starting-at bmap3 40) 10)
(should (phpinspect-bmap-token-starting-at bmap 50))
(phpinspect-bmap-overlay
bmap2 bmap (phpinspect-bmap-token-starting-at bmap 10) -3)
(phpinspect-bmap-overlay
bmap2 bmap (phpinspect-bmap-token-starting-at bmap 50) 5)
(should (eq 'token2 (phpinspect-meta-token
(phpinspect-bmap-token-starting-at bmap2 20))))
(should (eq 'token (phpinspect-meta-token
(phpinspect-bmap-token-starting-at bmap2 7))))
(should (phpinspect-bmap-token-meta bmap 'token))
(should (phpinspect-bmap-token-meta bmap2 'token2))
(should (phpinspect-bmap-token-meta bmap2 'token))
(should (phpinspect-bmap-token-meta bmap2 'token3))))
(ert-deftest phpinspect-bmap-nest-parent ()
(let ((bmap (phpinspect-make-bmap)))
(phpinspect-bmap-register bmap 10 20 'child)
(phpinspect-bmap-register bmap 5 25 'parent)
(phpinspect-bmap-register bmap 2 30 'granny)
(let ((child (phpinspect-bmap-token-meta bmap 'child))
(parent (phpinspect-bmap-token-meta bmap 'parent)))
(should (eq 'parent (phpinspect-meta-token
(phpinspect-meta-parent child))))
(should (eq 'granny (phpinspect-meta-token (phpinspect-meta-parent parent)))))))
(ert-deftest phpinspect-bmap-tokens-overlapping ()
(let ((bmap (phpinspect-make-bmap)))
(phpinspect-bmap-register bmap 9 200 'node1)
(phpinspect-bmap-register bmap 20 200 'node2)
(phpinspect-bmap-register bmap 9 20 'node3)
(phpinspect-bmap-register bmap 21 44 'node4)
(setq result (phpinspect-bmap-tokens-overlapping bmap 22))
(should (equal '(node4 node2 node1) (mapcar #'phpinspect-meta-token result)))))
(ert-deftest phpinspect-bmap-tokens-overlapping-overlayed ()
(let ((bmap (phpinspect-make-bmap))
(bmap2 (phpinspect-make-bmap))
(bmap3 (phpinspect-make-bmap)))
(phpinspect-bmap-register bmap 9 200 'token1)
(phpinspect-bmap-register bmap 20 200 'token2)
(phpinspect-bmap-register bmap 9 20 'token3)
(phpinspect-bmap-register bmap 21 44 'token4)
(phpinspect-bmap-register bmap2 200 230 'token5)
(phpinspect-bmap-register bmap3 300 305 'token6)
;; Should start at 220 of bmap2
(phpinspect-bmap-overlay
bmap2 bmap3 (phpinspect-bmap-token-starting-at bmap3 300) -80)
(setq result (phpinspect-bmap-tokens-overlapping bmap2 220))
(should (equal '(token6 token5) (mapcar #'phpinspect-meta-token result)))
(phpinspect-bmap-overlay
bmap bmap2 (phpinspect-bmap-token-starting-at bmap2 200) 20)
(setq result (phpinspect-bmap-tokens-overlapping bmap 240))
(should (equal '(token6 token5) (mapcar #'phpinspect-meta-token result)))
(setq result (phpinspect-bmap-tokens-overlapping bmap 22))
(should (equal '(token4 token2 token1) (mapcar #'phpinspect-meta-token result)))))
(ert-deftest phpinspect-bmap-register ()
(let* ((bmap (phpinspect-make-bmap))
(token1 `(:word "foo"))
(token2 `(:word "bar"))
(token3 `(:block ,token1 ,token2))
(token4 `(:list ,token3)))
(phpinspect-bmap-register bmap 10 20 token1)
(phpinspect-bmap-register bmap 20 30 token2)
(phpinspect-bmap-register bmap 9 31 token3)
(phpinspect-bmap-register bmap 8 32 token4)
(should (phpinspect-bmap-token-meta bmap token1))
(should (phpinspect-bmap-token-meta bmap token2))
(should (phpinspect-bmap-token-meta bmap token3))
(should (phpinspect-bmap-token-meta bmap token4))))

@ -1,4 +1,4 @@
;;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
@ -27,33 +27,27 @@
(require 'phpinspect-parser)
(require 'phpinspect-buffer)
(ert-deftest phpinspect-parse-buffer-location-map ()
"Confirm that the location map of `phpinspect-current-buffer' is
populated when the variable is set and the data in it is accurate."
(let* ((location-map)
(parsed)
(ert-deftest phpinspect-buffer-region-lookups ()
(let* ((parsed)
(class))
(with-temp-buffer
(insert-file-contents (concat phpinspect-test-php-file-directory "/NamespacedClass.php"))
(setq phpinspect-current-buffer
(phpinspect-make-buffer :buffer (current-buffer)))
(setq parsed (phpinspect-buffer-parse phpinspect-current-buffer))
(setq location-map
(phpinspect-buffer-location-map phpinspect-current-buffer)))
(let* ((class (seq-find #'phpinspect-class-p
(seq-find #'phpinspect-namespace-p parsed)))
(class-region (gethash class location-map))
(classname-region (gethash (car (cddadr class)) location-map)))
(should class)
(should class-region)
(should classname-region)
;; Character position of the start of the class token.
(should (= 611 (phpinspect-region-start class-region)))
(should (= 2367 (phpinspect-region-end class-region)))
(should (= 617 (phpinspect-region-start classname-region)))
(should (= 634 (phpinspect-region-end classname-region))))))
(let* ((class (seq-find #'phpinspect-class-p
(seq-find #'phpinspect-namespace-p parsed)))
(classname (car (cddadr class))))
(let ((tokens (phpinspect-buffer-tokens-enclosing-point
phpinspect-current-buffer 617)))
(should (eq classname
(phpinspect-meta-token (car tokens))))
(should (phpinspect-declaration-p (phpinspect-meta-token (cadr tokens))))
(should (eq class (phpinspect-meta-token (caddr tokens)))))))))
(ert-deftest phpinspect-parse-buffer-no-current ()
"Confirm that the parser is still functional with
@ -66,3 +60,77 @@ populated when the variable is set and the data in it is accurate."
(setq parsed (phpinspect-parse-current-buffer)))
(should (cdr parsed))))
(cl-defstruct (phpinspect-document (:constructor phpinspect-make-document))
(buffer (get-buffer-create
(generate-new-buffer-name " **phpinspect-document** shadow buffer") t)
:type buffer
:documentation
"A hidden buffer with a reference version of the document."))
(cl-defmethod phpinspect-document-apply-edit
((document phpinspect-document) start end delta contents)
(with-current-buffer (phpinspect-document-buffer document)
(goto-char start)
(delete-region (point) (- end delta))
(insert contents)))
(cl-defmethod phpinspect-document-set-contents
((document phpinspect-document) (contents string))
(with-current-buffer (phpinspect-document-buffer document)
(erase-buffer)
(insert contents)))
(cl-defmethod phpinspect-document-contents ((document phpinspect-document))
(with-current-buffer (phpinspect-document-buffer document)
(buffer-string)))
(ert-deftest phpinspect-buffer-parse-incrementally ()
(let* ((document (phpinspect-make-document))
(buffer (phpinspect-make-buffer
:buffer (phpinspect-document-buffer document)))
(parsed))
;; TODO: write tests for more complicated cases (multiple edits, etc.)
(phpinspect-document-set-contents document "<?php function Bello() { echo 'Hello World!'; if ($name) { echo 'Hello ' . $name . '!';} }")
(setq parsed (phpinspect-buffer-parse buffer))
(should parsed)
(let* ((enclosing-bello (phpinspect-buffer-tokens-enclosing-point buffer 18))
(bello (car enclosing-bello))
(enclosing-bello1)
(bello1)
(bello2))
(should (equal '(:word "Bello") (phpinspect-meta-token bello)))
(should parsed)
;; Delete function block opening brace
(phpinspect-document-apply-edit document 24 24 -1 "")
(should (string= "<?php function Bello() echo 'Hello World!'; if ($name) { echo 'Hello ' . $name . '!';} }"
(phpinspect-document-contents document)))
(phpinspect-buffer-register-edit buffer 24 24 1)
(setq parsed (phpinspect-buffer-parse buffer))
(should parsed)
(setq enclosing-bello1 (phpinspect-buffer-tokens-enclosing-point buffer 18))
(setq bello1 (car enclosing-bello1))
(should (eq (phpinspect-meta-token bello) (phpinspect-meta-token bello1)))
(should (phpinspect-declaration-p (phpinspect-meta-token (phpinspect-meta-parent bello))))
(should (phpinspect-declaration-p (phpinspect-meta-token (phpinspect-meta-parent bello1))))
(should (phpinspect-function-p (phpinspect-meta-token (phpinspect-meta-parent (phpinspect-meta-parent bello)))))
(should (phpinspect-function-p (phpinspect-meta-token (phpinspect-meta-parent (phpinspect-meta-parent bello1)))))
(let ((function (phpinspect-meta-token (phpinspect-meta-parent (phpinspect-meta-parent bello1)))))
(should (= 2 (length function)))
(should (phpinspect-declaration-p (cadr function)))
(should (member '(:word "Bello") (cadr function))))
(phpinspect-document-apply-edit document 24 25 1 "{")
(should (string= "<?php function Bello() { echo 'Hello World!'; if ($name) { echo 'Hello ' . $name . '!';} }"
(phpinspect-document-contents document)))
(phpinspect-buffer-register-edit buffer 24 25 0)
(setq parsed (phpinspect-buffer-parse buffer))
(should parsed)
(setq bello2 (car (phpinspect-buffer-tokens-enclosing-point buffer 18)))
(should (eq (phpinspect-meta-token bello) (phpinspect-meta-token bello2))))))

@ -0,0 +1,75 @@
(require 'ert)
(require 'phpinspect-edtrack)
(ert-deftest phpinspect-edit-end ()
(let ((edit (list (cons 10 3) (cons 6 5) (cons 4 -2))))
(should (= 13 (phpinspect-edit-end edit)))))
(ert-deftest phpinspect-edtrack-register-edit ()
(let* ((edtrack (phpinspect-make-edtrack))
(edit1 (phpinspect-edtrack-register-edit edtrack 5 10 10))
(edit3 (phpinspect-edtrack-register-edit edtrack 100 200 150))
(edit2 (phpinspect-edtrack-register-edit edtrack 15 22 7)))
(should (equal `((255 . -50) (27 . 0) (15 . -5)) (phpinspect-edtrack-edits edtrack)))))
;; (pp (phpinspect-edtrack-edits edtrack))
;; (should (= 10 (phpinspect-edit-end edit1)))
;; (should (= 22 (phpinspect-edit-end edit2)))
;; (should (= 30 (phpinspect-edtrack-original-position-at-point edtrack 25)))
;; (should (= 4 (phpinspect-edtrack-original-position-at-point edtrack 4)))
;; (should (= 260 (phpinspect-edtrack-original-position-at-point edtrack 205)))))
(ert-deftest phpinsepct-edtrack-register-multi-edits ()
(let ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit track 10 20 5)
(phpinspect-edtrack-register-edit track 25 30 0)
(phpinspect-edtrack-register-edit track 13 20 0)
(should (= 42 (phpinspect-edtrack-current-position-at-point track 25)))))
(ert-deftest phpinspect-edtrack-register-multi-edits-deletions ()
(let ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit track 10 20 5)
(phpinspect-edtrack-register-edit track 25 30 20)
(phpinspect-edtrack-register-edit track 13 20 0)
(should (= 42 (phpinspect-edtrack-current-position-at-point track 45)))))
(ert-deftest phpinspect-edtrack-register-taint ()
(let* ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-taint track 0 5)
(phpinspect-edtrack-register-taint track 10 20)
(should (equal (list (cons 0 5) (cons 10 20)) (phpinspect-edtrack-taint-pool track)))
(phpinspect-edtrack-register-taint track 3 20)
(should (equal (list (cons 0 20)) (phpinspect-edtrack-taint-pool track)))))
(ert-deftest phpinspect-edtrack-taint-iterator ()
(let ((track (phpinspect-make-edtrack))
(iterator))
(phpinspect-edtrack-register-taint track 120 150)
(phpinspect-edtrack-register-taint track 5 30)
(phpinspect-edtrack-register-taint track 25 50)
(phpinspect-edtrack-register-taint track 70 100)
(setq iterator (phpinspect-edtrack-make-taint-iterator track))
(should-not (phpinspect-taint-iterator-token-is-tainted-p
iterator (phpinspect-make-meta nil 1 4 nil nil)))
(should (phpinspect-taint-iterator-token-is-tainted-p
iterator (phpinspect-make-meta nil 4 7 nil nil)))
(should (phpinspect-taint-iterator-token-is-tainted-p
iterator (phpinspect-make-meta nil 20 30 nil nil)))
(should-not (phpinspect-taint-iterator-token-is-tainted-p
iterator (phpinspect-make-meta nil 51 55 nil nil)))
(should (phpinspect-taint-iterator-token-is-tainted-p
iterator (phpinspect-make-meta nil 65 73 nil nil)))
(should (phpinspect-taint-iterator-token-is-tainted-p
iterator (phpinspect-make-meta nil 100 130 nil nil)))))

@ -145,3 +145,39 @@ return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->anti
(should (phpinspect--type=
(phpinspect--make-type :name "\\void" :fully-qualified t)
(phpinspect--function-return-type method))))))))
(ert-deftest phpinspect-index-tokens-class ()
(let* ((index1
(phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1")))
(index2
(phpinspect-test-read-fixture-serialization "IndexClass1-indexed"))
(index1-class (car (alist-get 'classes index1)))
(index2-class (car (alist-get 'classes index2))))
(dolist (key '(class-name imports methods static-methods static-variables variables constants extends implements))
(should (equal (alist-get key index1-class)
(alist-get key index2-class))))))
(ert-deftest phpinspect-index-bmap-class ()
(let* ((pctx (phpinspect-make-pctx :incremental t))
(tree))
(with-temp-buffer
(insert-file-contents (concat phpinspect-test-php-file-directory "/IndexClass1.php"))
(setf (phpinspect-pctx-bmap pctx) (phpinspect-make-bmap))
(phpinspect-with-parse-context pctx (setq tree (phpinspect-parse-current-buffer))))
(let* ((index1 (phpinspect--index-tokens tree
nil
(phpinspect-bmap-make-location-resolver
(phpinspect-pctx-bmap pctx))))
(index2
(phpinspect-test-read-fixture-serialization "IndexClass1-indexed"))
(index1-class (car (alist-get 'classes index1)))
(index2-class (car (alist-get 'classes index2))))
(dolist (key '(imports methods static-methods static-variables variables constants extends implements))
(should (equal (alist-get key index1-class)
(alist-get key index2-class))))
(should (alist-get 'location index1-class))
(should (alist-get 'location index1-class)))))

@ -0,0 +1,32 @@
(require 'phpinspect-parser)
(ert-deftest phpinspect-parse-bmap ()
(let* ((ctx (phpinspect-make-pctx :incremental t))
(code "
class TestClass {
public function getCurrentStatisticAction(): JsonResponse
{
$statistic = $this->repository->getCurrentStatistic();
if (!$this->authorization->isGranted(EntityAction::VIEW, $statistic)) {
return $this->responder->respondUnauthorized();
}
return $this->responder->respond($statistic);
}
}")
(bmap))
(phpinspect-with-parse-context ctx
(phpinspect-parse-string code))
(setq bmap (phpinspect-pctx-bmap ctx))
(let ((enclosing (phpinspect-bmap-tokens-overlapping bmap 350))
(parent))
(should enclosing)
(should (phpinspect-variable-p (phpinspect-meta-token (car enclosing))))
(should (string= "statistic" (cadr (phpinspect-meta-token (car enclosing)))))
(should (phpinspect-meta-parent (car enclosing)))
(setq parent (phpinspect-meta-parent (car enclosing)))
(should (phpinspect-list-p (phpinspect-meta-token parent)))
(should (phpinspect-block-p (phpinspect-meta-token (phpinspect-meta-parent parent)))))))

@ -0,0 +1,109 @@
(require 'phpinspect-resolvecontext)
(ert-deftest phinspect-get-resolvecontext ()
(let* ((ctx (phpinspect-make-pctx :incremental t))
(code "
class TestClass {
public function getCurrentStatisticAction(): JsonResponse
{
$statistic = $this->repository->getCurrentStatistic();
if (!$this->authorization->isGranted(EntityAction::VIEW, $statistic)) {
return $this->responder->respondUnauthorized();
}
$this->
return $this->responder->respond($statistic);
}
}")
(bmap))
(phpinspect-with-parse-context ctx
(phpinspect-parse-string code))
(setq bmap (phpinspect-pctx-bmap ctx))
(let ((rctx (phpinspect-get-resolvecontext bmap 317)))
(should (phpinspect--resolvecontext-subject rctx))
(should (phpinspect--resolvecontext-enclosing-tokens rctx)))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext ()
(with-temp-buffer
(insert-file-contents (concat phpinspect-test-php-file-directory "/IncompleteClass.php"))
(let* ((bmap (phpinspect-parse-string-to-bmap (buffer-string)))
(resolvecontext (phpinspect-get-resolvecontext bmap (point-max)))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver
(phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver
(phpinspect--make-type :name "GastonLagaffe"))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver
(phpinspect--make-type :name "Dupuis\\GastonLagaffe")))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext-namespace-block ()
(with-temp-buffer
(insert-file-contents (concat phpinspect-test-php-file-directory "/IncompleteClassBlockedNamespace.php"))
(let* ((bmap (phpinspect-parse-string-to-bmap (buffer-string)))
(resolvecontext (phpinspect-get-resolvecontext bmap (point-max)))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver (phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver (phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver (phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type
:name "GastonLagaffe"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type :name "Dupuis\\GastonLagaffe")))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext-multiple-namespace-blocks ()
(with-temp-buffer
(insert-file-contents (concat phpinspect-test-php-file-directory "/IncompleteClassMultipleNamespaces.php"))
(let* ((bmap (phpinspect-parse-string-to-bmap (buffer-string)))
(resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data
"IncompleteClassMultipleNamespaces")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver
(phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type :name "GastonLagaffe"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type
:name "Dupuis\\GastonLagaffe")))))))
Loading…
Cancel
Save