Implement Incremental Parsing
ci/woodpecker/push/woodpecker Pipeline was successful Details

WIP-cache
Hugo Thunnissen 10 months ago
parent 0ca527dbbd
commit ad5ede01ad

File diff suppressed because it is too large Load Diff

@ -0,0 +1,54 @@
(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
(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 "Bare (no token reuse) parse (warmup):")
(benchmark 1 '(phpinspect-parse-current-buffer))
(message "Bare (no token reuse) parse:")
(benchmark 1 '(phpinspect-parse-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