Compare commits

..

1 Commits
master ... WIP

Author SHA1 Message Date
Hugo Thunnissen f7f5776f98 WIP 3 years ago

10
.gitignore vendored

@ -1,9 +1 @@
*.elc
/benchmarks/profile.txt
/.deps
/data
# ELPA-generated files
/phpinspect-autoloads.el
/phpinspect-pkg.el
*.elc

@ -1,5 +0,0 @@
pipeline:
test:
image: silex/emacs:28.1-ci
commands:
- emacs -L ./ -batch -l ert -l ./phpinspect.el -l ./test/phpinspect-test.el -f ert-run-tests-batch-and-exit

@ -1,52 +0,0 @@
export EMACS ?= $(shell which emacs)
ELC_FILES = $(patsubst %.el, %.elc, $(shell ls -1 ./*.el ./test/*.el ./benchmarks/*.el))
DEP_DIRECTORY = $(CURDIR)/.deps
RUN_EMACS := emacs -batch -L $(CURDIR) --eval '(package-initialize)'
export HOME = ${DEP_DIRECTORY}
$(CURDIR): deps
$(CURDIR):$(ELC_FILES)
$(CURDIR): ./data/builtin-stubs-index.eld.gz
./.deps: ./phpinspect.el
./.deps:
emacs -batch -l ./scripts/install-deps.el
./stubs/builtins.php: ./scripts/generate-builtin-stubs.php
mkdir -p ./stubs/
php ./scripts/generate-builtin-stubs.php > ./stubs/builtins.php
./data/builtin-stubs-index.eld.gz: ./stubs/builtins.php | ./.deps
mkdir -p ./data/
$(RUN_EMACS) -l phpinspect-cache -f phpinspect-dump-stub-index
%.elc: %.el
$(RUN_EMACS) --eval '(setq byte-compile-error-on-warn t)' -f batch-byte-compile $<
.PHONY: deps
deps: ./.deps
.PHONY: stub-index
stub-index: ./data/builtin-stubs-index.eld.gz
.PHONY: clean
clean:
rm -f $(ELC_FILES) ./data/builtin-stubs-index.eld.gz
.PHONY: clean-all
clean-all: clean
rm -f ./stubs/builtins.php
.PHONY: compile
compile: ./.deps
compile: $(ELC_FILES)
.PHONY: compile-native
compile-native: ./.deps
bash ./scripts/native-compile.bash
.PHONY: test
test: deps
$(RUN_EMACS) -L ./test -l ./test/phpinspect-test e -f ert-run-tests-batch

@ -1,46 +1,11 @@
# phpinspect.el
PHPInspect is a minor mode that provides code intelligence for PHP in Emacs. At
its core is a PHP parser implemented in Emacs Lisp. PHPInspect comes with
backends for `completion-at-point`, `company-mode` and `eldoc`. A backend for
`xref` (which provides go-to-definition functionality) is planned to be
implemented at a later date. The main documentation of the mode is in the
docstring of the mode itself (`C-h f phpinspect-mode RET` to view, or read it in
the source code of [phpinspect.el](phpinspect.el)).
## Projects and Finding Types
By default, phpinspect will recognize composer projects and read their
composer.json files for autoload information which is used to find files in
which the types/classes/functions you use in your code are defined. It is also
possible to add an "include directory" of files that should always be read and
indexed for a certain project. To do this, open a file in a project and run `M-x
phpinspect-project-add-include-dir`. You can also edit the list of include
directories via `M-x customize-goup RET phpinspect RET`.
WIP. More documentation is in the making.
## Example Configuration
If you already have a completion UI setup that is able to use
`completion-at-point-functions` as completion source, you can basically just
enable phpinspect-mode and you'll be good to go. An example of a basic mode hook
configuration to get the most out of phpinspect is the following:
For bug reports and/or feature requests visit the [ticket tracker](https://todo.sr.ht/~hugot/phpinspect.el)
```elisp
(defun my-php-personal-hook ()
;; Shortcut to add use statements for classes you use.
(define-key php-mode-map (kbd \"C-c u\") 'phpinspect-fix-imports)
;; Shortcuts to quickly search/open files of PHP classes.
;; You can make these local to php-mode, but making them global
;; like this makes them work in other modes/filetypes as well, which
;; can be handy when jumping between templates, config files and PHP code.
(global-set-key (kbd \"C-c a\") 'phpinspect-find-class-file)
(global-set-key (kbd \"C-c c\") 'phpinspect-find-own-class-file)
;; Enable phpinspect-mode
(phpinspect-mode))
(add-hook 'php-mode-hook #'my-php-personal-hook)
```
## Example config with company mode setup
## Example config
```elisp
;;;###autoload
@ -54,7 +19,7 @@ configuration to get the most out of phpinspect is the following:
(setq-local company-backends '(phpinspect-company-backend))
;; Shortcut to add use statements for classes you use.
(define-key php-mode-map (kbd "C-c u") 'phpinspect-fix-imports)
(define-key php-mode-map (kbd "C-c u") 'phpinspect-fix-uses-interactive)
;; Shortcuts to quickly search/open files of PHP classes.
(global-set-key (kbd "C-c a") 'phpinspect-find-class-file)
@ -65,12 +30,10 @@ configuration to get the most out of phpinspect is the following:
(add-hook 'php-mode-hook #'my-php-personal-hook)
```
## Install from git
## Install
```bash
git clone https://git.snorba.art/hugo/phpinspect.el ~/projects/phpinspect.el
cd ~/projects/phpinspect.el
make
git clone https://git.sr.ht/~hugot/phpinspect.el ~/projects/phpinspect.el
```
```elisp
@ -78,67 +41,12 @@ make
(require 'phpinspect)
```
## Compilation
It is highly recommended to byte- or native compile phpinspect. Aside from the
normal performance boost that this brings to most packages, it can reduce
phpinspect's parsing time by up to 90%. It especially makes a difference when
incrementally parsing edited buffers. For example:
### benchmarks/parse-file.el uncompiled on Ryzen 5 3600 (time in seconds):
```
Incremental parse (warmup):
Elapsed time: 0.168390 (0.019751 in 1 GCs)
Incremental parse:
Elapsed time: 0.143811 (0.000000 in 0 GCs)
Incremental parse (no edits):
Elapsed time: 0.000284 (0.000000 in 0 GCs)
Incremental parse repeat (no edits):
Elapsed time: 0.000241 (0.000000 in 0 GCs)
Incremental parse after buffer edit:
Elapsed time: 0.012449 (0.000000 in 0 GCs)
Incremental parse after 2 more edits:
Elapsed time: 0.015839 (0.000000 in 0 GCs)
Bare (no token reuse) parse (warmup):
Elapsed time: 0.048996 (0.000000 in 0 GCs)
Bare (no token reuse) parse:
Elapsed time: 0.052495 (0.000000 in 0 GCs)
```
### benchmarks/parse-file.el with native compilation on Ryzen 5 3600 (time in seconds):
```
Incremental parse (warmup):
Elapsed time: 0.023432 (0.000000 in 0 GCs)
Incremental parse:
Elapsed time: 0.018350 (0.000000 in 0 GCs)
Incremental parse (no edits):
Elapsed time: 0.000076 (0.000000 in 0 GCs)
Incremental parse repeat (no edits):
Elapsed time: 0.000058 (0.000000 in 0 GCs)
Incremental parse after buffer edit:
Elapsed time: 0.001212 (0.000000 in 0 GCs)
Incremental parse after 2 more edits:
Elapsed time: 0.001381 (0.000000 in 0 GCs)
Bare (no token reuse) parse (warmup):
Elapsed time: 0.013874 (0.000000 in 0 GCs)
Bare (no token reuse) parse:
Elapsed time: 0.013878 (0.000000 in 0 GCs)
```
## Development
### Building
```bash
make
```
### Running tests
Tests are implemented using `ert`. You can run them in batch mode with the following
command:
```bash
make test
# or:
emacs -L ./ -batch -l ert -l ./test/phpinspect-test.el -f ert-run-tests-batch-and-exit
emacs -batch -l ert -l ./phpinspect.el -l ./test/phpinspect-test.el -f ert-run-tests-batch-and-exit
```

File diff suppressed because it is too large Load Diff

@ -1,53 +0,0 @@
;;; appendage.el --- Benchmarks of list appendage -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: benchmark
;; 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:
(message "20000 appendages using nconc")
(garbage-collect)
(benchmark
1 '(let (list)
(dotimes (i 20000)
(setq list (nconc list (list i))))
list))
(message "20000 appendages using push + nreverse")
(garbage-collect)
(benchmark
1 '(let (list)
(dotimes (i 20000)
(push i list))
(nreverse list)))
(message "20000 appendages using rear pointer")
(garbage-collect)
(benchmark
1 '(let* ((list (cons nil nil))
(rear list))
(dotimes (i 20000)
(setq rear (setcdr rear (cons i nil))))
(cdr list)))

@ -1,137 +0,0 @@
;;; parse-file.el --- Benchmarks of phpinspect parser in different configurations -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: benchmark
;; 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-parser)
(defun phpinspect-parse-current-buffer ()
(phpinspect-parse-buffer-until-point
(current-buffer)
(point-max)))
(let* ((here (file-name-directory (macroexp-file-name)))
(benchmark-file (or (getenv "PHPINSPECT_BENCHMARK_FILE")
(expand-file-name "Response.php" here)))
result)
(with-temp-buffer
(insert-file-contents benchmark-file)
(message "Incremental parse (warmup):")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap (phpinspect-make-bmap))
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(let ((bmap (phpinspect-make-bmap))
(bmap2 (phpinspect-make-bmap)))
(message "Incremental parse:")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap)
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(garbage-collect)
(message "Incremental parse (no edits):")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap bmap2
:previous-bmap bmap
:edtrack (phpinspect-make-edtrack))
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(garbage-collect)
(message "Incremental parse repeat (no edits):")
(phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap (phpinspect-make-bmap)
:previous-bmap bmap2
:edtrack (phpinspect-make-edtrack))
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(garbage-collect)
(let ((edtrack (phpinspect-make-edtrack))
(bmap (phpinspect-make-bmap))
(bmap-after (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-char -1)
(garbage-collect)
(phpinspect-edtrack-register-edit edtrack 9061 9061 1)
(phpinspect-with-parse-context (phpinspect-make-pctx :bmap bmap-after :incremental t :previous-bmap bmap :edtrack edtrack)
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(phpinspect-edtrack-clear edtrack)
(insert "{")
(phpinspect-edtrack-register-edit edtrack 9061 9062 0)
;; Mark region as edit without length deta
(phpinspect-edtrack-register-edit edtrack 19552 19562 10)
(garbage-collect)
;;(profiler-start 'cpu)
(message "Incremental parse after 2 more edits:")
(phpinspect-with-parse-context (phpinspect-make-pctx :bmap (phpinspect-make-bmap)
:incremental t
:previous-bmap bmap-after
:edtrack edtrack)
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
;; (save-current-buffer
;; (profiler-stop)
;; (profiler-report)
;; (profiler-report-write-profile (expand-file-name "profile.txt" here)))
)))
(with-temp-buffer
(insert-file-contents benchmark-file)
(garbage-collect)
(message "Bare (no token reuse) parse (warmup):")
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer)))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(garbage-collect)
(message "Bare (no token reuse) parse:")
(setq result (benchmark-run 1 (phpinspect-parse-current-buffer))))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result)))

@ -1,103 +0,0 @@
;;; splay-tree.el --- Benchmarks of phpinspect-splayt.el -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: benchmark
;; 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-splayt)
(let ((tree (phpinspect-make-splayt))
result)
(message "Splay tree 10000 insertions:")
(garbage-collect)
(setq result
(benchmark-run 1
(dotimes (i 10000)
(phpinspect-splayt-insert tree i 'value))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result))
(message "Splay tree 10000 lookups:")
(garbage-collect)
(setq result
(benchmark-run 1
(dotimes (i 10000)
(phpinspect-splayt-find tree i))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result))
(message "Splay tree 10000 items traversal:")
(garbage-collect)
(setq result
(benchmark-run 1
(phpinspect-splayt-traverse (i tree)
(ignore i))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result))
(message "Splay tree 10000 items LR traversal:")
(garbage-collect)
(setq result
(benchmark-run 1
(phpinspect-splayt-traverse-lr (i tree)
(ignore i))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result)))
(let (map result)
(message "Hashtable 10000 insertions:")
(garbage-collect)
(setq result
(benchmark-run 1
(progn
(setq map (make-hash-table :test #'eq :size 10000 :rehash-size 1.5))
(dotimes (i 10000)
(puthash i 'value map)))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result))
(message "Hashtable 10000 lookups:")
(garbage-collect)
(setq result
(benchmark-run 1
(dotimes (i 10000)
(ignore (gethash i map)))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result))
(message "Hashtable 10000 iterations:")
(garbage-collect)
(setq result
(benchmark-run 1
(ignore (maphash (lambda (k v) k v) map))))
(message "Elapsed time: %f (%f in %d GC's)"
(car result) (caddr result) (cadr result)))

@ -1,51 +0,0 @@
;;; stubs.el --- Benchmarks of phpinspect stub index dump and load times -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: benchmark
;; 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-cache)
(let (result)
(message "Building and loading stub cache")
(garbage-collect)
(setq result
(benchmark-run 1 (phpinspect-build-stub-cache)))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(message "Building stub cache")
(garbage-collect)
(setq result
(benchmark-run 1 (phpinspect-build-stub-index)))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(message "Building and dumping stub cache")
(garbage-collect)
(setq result
(benchmark-run 1 (phpinspect-dump-stub-index)))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result))
(message "Loading stub cache")
(garbage-collect)
(setq result
(benchmark-run 1 (phpinspect-load-stub-index)))
(message "Elapsed time: %f (%f in %d GC's)" (car result) (caddr result) (cadr result)))

@ -1,288 +0,0 @@
;;; phpinspect-autoload.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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 'cl-lib)
(require 'phpinspect-fs)
(require 'phpinspect-util)
(require 'phpinspect-pipeline)
(require 'json)
(cl-defstruct (phpinspect-psr0
(:constructor phpinspect-make-psr0-generated))
(prefix nil
:type string
:documentation "The namespace prefix for which the directories contain code.")
(autoloader nil
:type phpinspect-autoloader)
(own nil
:type boolean)
(fs nil
:type phpinspect-fs)
(directories nil
:type list
:documentation
"The directories that this autoloader finds code in."))
(cl-defstruct (phpinspect-psr4
(:constructor phpinspect-make-psr4-generated))
(prefix nil
:type string
:documentation "The namespace prefix for which the directories contain code.")
(autoloader nil
:type phpinspect-autoloader)
(own nil
:type boolean)
(fs nil
:type phpinspect-fs)
(directories nil
:type list
:documentation
"The directories that this autoloader finds code in."))
(cl-defstruct (phpinspect-files (:constructor phpinspect-make-files))
(autoloader nil)
(list nil
:type list
:documentation
"List of files to be indexed"))
(cl-defstruct (phpinspect-autoloader
(:constructor phpinspect-make-autoloader))
(refresh-thread nil
:type thread)
(fs nil
:type phpinspect-fs)
(file-indexer nil
:type function)
(project-root-resolver nil
:type function)
(own-types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
:type hash-table
:documentation "The internal types that can be
autoloaded through this autoloader")
(types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
:type hash-table
:documentation
"The external types that can be autoloaded through this autoloader.")
(type-name-fqn-bags (make-hash-table :test 'eq :size 3000 :rehash-size 3000)
:type hash-table
:documentation
"Hash table that contains lists of fully
qualified names congruent with a bareword type name. Keyed by
bareword typenames."))
(cl-defmethod phpinspect--read-json-file (fs file)
(with-temp-buffer
(phpinspect-fs-insert-file-contents fs file)
(goto-char 0)
(phpinspect-json-preset (json-read))))
(define-inline phpinspect-filename-to-typename (dir filename &optional prefix)
(inline-quote
(phpinspect-intern-name
(replace-regexp-in-string
"\\\\[\\]+"
"\\\\"
(concat "\\"
(or ,prefix "")
(replace-regexp-in-string
"/" "\\\\"
(string-remove-suffix
".php"
(string-remove-prefix ,dir ,filename))))))))
(defun phpinspect-find-composer-json-files (fs project-root)
(let ((cj-path (concat project-root "/composer.json"))
(vendor-dir (concat project-root "/vendor"))
files)
(when (phpinspect-fs-file-exists-p fs cj-path)
(push `(local . ,cj-path) files))
(when (phpinspect-fs-file-directory-p fs vendor-dir)
(dolist (author-dir (phpinspect-fs-directory-files fs vendor-dir))
(when (phpinspect-fs-file-directory-p fs author-dir)
(dolist (dependency-dir (phpinspect-fs-directory-files fs author-dir))
(setq cj-path (concat dependency-dir "/composer.json"))
(when (and (phpinspect-fs-file-directory-p fs dependency-dir)
(phpinspect-fs-file-exists-p fs cj-path))
(push `(vendor . ,cj-path) files))))))
files))
(cl-defmethod phpinspect-al-strategy-execute ((strat phpinspect-psr4))
(let* ((fs (phpinspect-psr4-fs strat))
(al (phpinspect-psr4-autoloader strat))
(own (phpinspect-psr4-own strat))
(own-typehash (phpinspect-autoloader-own-types al))
(typehash (phpinspect-autoloader-types al))
(prefix (phpinspect-psr4-prefix strat))
type-fqn)
(dolist (dir (phpinspect-psr4-directories strat))
(when (phpinspect-fs-file-directory-p fs dir)
(dolist (file (phpinspect-fs-directory-files-recursively fs dir "\\.php$"))
(setq type-fqn (phpinspect-filename-to-typename dir file prefix))
(phpinspect-autoloader-put-type-bag al type-fqn)
(puthash type-fqn file typehash)
(when own
(puthash type-fqn file own-typehash)))))))
(cl-defmethod phpinspect-al-strategy-execute ((strat phpinspect-psr0))
(let* ((fs (phpinspect-psr0-fs strat))
(al (phpinspect-psr0-autoloader strat))
(own (phpinspect-psr0-own strat))
(own-typehash (phpinspect-autoloader-own-types al))
(typehash (phpinspect-autoloader-types al))
type-fqn)
(dolist (dir (phpinspect-psr0-directories strat))
(dolist (file (phpinspect-fs-directory-files-recursively fs dir "\\.php$"))
(setq type-fqn (phpinspect-filename-to-typename dir file))
(phpinspect-autoloader-put-type-bag al type-fqn)
(puthash type-fqn file typehash)
(when own
(puthash type-fqn file own-typehash))))))
(cl-defmethod phpinspect-al-strategy-execute ((strat phpinspect-files))
(phpinspect--log "indexing files list: %s" (phpinspect-files-list strat))
(let* ((indexer (phpinspect-autoloader-file-indexer (phpinspect-files-autoloader strat))))
(phpinspect-pipeline (phpinspect-files-list strat)
:into (funcall :with-context indexer))))
(cl-defmethod phpinspect-autoloader-put-type-bag ((al phpinspect-autoloader) (type-fqn (head phpinspect-name)))
(let* ((type-name (phpinspect-intern-name
(car (last (split-string (phpinspect-name-string type-fqn) "\\\\")))))
(bag (gethash type-name (phpinspect-autoloader-type-name-fqn-bags al))))
(if bag
(setcdr bag (cons type-fqn (cdr bag)))
(push type-fqn bag)
(puthash type-name bag (phpinspect-autoloader-type-name-fqn-bags al)))))
(cl-defmethod phpinspect-autoloader-get-type-bag ((al phpinspect-autoloader) (type-name (head phpinspect-name)))
(gethash type-name (phpinspect-autoloader-type-name-fqn-bags al)))
(cl-defmethod phpinspect-iterate-composer-jsons
((al phpinspect-autoloader) file)
(let* ((fs (phpinspect-autoloader-fs al))
(project-root (file-name-directory (cdr file)))
json autoload batch)
(condition-case err
(setq json (phpinspect--read-json-file fs (cdr file)))
(t (phpinspect-message "Error parsing composer json at %s : %s " (cdr file) err)))
(when json
(setq autoload (gethash "autoload" json))
(when (hash-table-p autoload)
(maphash
(lambda (type prefixes)
(let ((strategy))
(pcase type
("psr-0"
(maphash
(lambda (prefix directory-paths)
(when (stringp directory-paths)
(setq directory-paths (list directory-paths)))
(setq strategy (phpinspect-make-psr0-generated
:autoloader al
:fs fs
:prefix prefix
:own (eq 'local (car file))))
(dolist (path directory-paths)
(push (file-name-concat project-root path)
(phpinspect-psr0-directories strategy)))
(push strategy batch))
prefixes))
("psr-4"
(maphash
(lambda (prefix directory-paths)
(when (stringp directory-paths)
(setq directory-paths (list directory-paths)))
(setq strategy (phpinspect-make-psr4-generated
:fs fs
:autoloader al
:prefix prefix
:own (eq 'local (car file))))
(dolist (path directory-paths)
(push (file-name-concat project-root path)
(phpinspect-psr4-directories strategy)))
(push strategy batch))
prefixes))
("files"
(setq strategy
(phpinspect-make-files
:list (mapcar
(lambda (file) (file-name-concat project-root file))
prefixes)
:autoloader al))
(push strategy batch))
(_ (phpinspect--log "Unsupported autoload strategy \"%s\" encountered" type)))))
autoload)
(phpinspect-pipeline-emit-all batch)))))
(cl-defmethod phpinspect-autoloader-resolve ((autoloader phpinspect-autoloader)
(typename (head phpinspect-name)))
;; Wait for pending refresh if not running in main thread.
(unless (eq main-thread (current-thread))
(when (and (phpinspect-autoloader-refresh-thread autoloader)
(thread-live-p (phpinspect-autoloader-refresh-thread autoloader)))
(phpinspect--log
"Pausing thread %s to await autoload refresh completion"
(thread-name (current-thread)))
(thread-join (phpinspect-autoloader-refresh-thread autoloader))
(phpinspect--log "Autoload refresh completed, continuing waiting thread %s"
(thread-name (current-thread)))))
(or (gethash typename (phpinspect-autoloader-own-types autoloader))
(gethash typename (phpinspect-autoloader-types autoloader))))
(cl-defmethod phpinspect-autoloader-refresh ((autoloader phpinspect-autoloader) &optional async-callback)
"Refresh autoload definitions by reading composer.json files
from the project and vendor folders."
(let* ((project-root (funcall (phpinspect-autoloader-project-root-resolver autoloader)))
(fs (phpinspect-autoloader-fs autoloader)))
(setf (phpinspect-autoloader-type-name-fqn-bags autoloader)
(make-hash-table :test 'eq :size 3000 :rehash-size 3000))
(setf (phpinspect-autoloader-own-types autoloader)
(make-hash-table :test 'eq :size 10000 :rehash-size 10000))
(setf (phpinspect-autoloader-types autoloader)
(make-hash-table :test 'eq :size 10000 :rehash-size 10000))
(setf (phpinspect-autoloader-refresh-thread autoloader)
(phpinspect-pipeline (phpinspect-find-composer-json-files fs project-root)
:async (or async-callback
(lambda (_result error)
(if error
(phpinspect-message "Error during autoloader refresh: %s" error)
(phpinspect-message
(concat "Refreshed project autoloader. Found %d types within project,"
" %d types total.")
(hash-table-count (phpinspect-autoloader-own-types autoloader))
(hash-table-count (phpinspect-autoloader-types autoloader))))))
:into (phpinspect-iterate-composer-jsons :with-context autoloader)
:into phpinspect-al-strategy-execute))))
(provide 'phpinspect-autoload)
;;; phpinspect-autoload.el ends here

@ -1,288 +0,0 @@
;;; phpinspect-bmap.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-splayt)
(require 'phpinspect-meta)
(require 'phpinspect-changeset)
(require 'phpinspect-parse-context)
(require 'phpinspect-util)
(require 'compat)
(require 'phpinspect-token-predicates)
(eval-when-compile
(defvar phpinspect-parse-context nil
"dummy for compilation")
(declare-function phpinspect-pctx-register-changeset "phpinspect-parse-context" (pctx changeset))
(phpinspect--declare-log-group 'bmap))
(cl-defstruct (phpinspect-bmap (:constructor phpinspect-make-bmap))
(starts (make-hash-table :test #'eql
:size (floor (/ (point-max) 2))
:rehash-size 1.5))
(ends (make-hash-table :test #'eql
:size (floor (/ (point-max) 2))
:rehash-size 1.5))
(meta (make-hash-table :test #'eq
:size (floor (/ (point-max) 2))
:rehash-size 1.5))
(token-stack nil
:type list)
(overlays (phpinspect-make-splayt)
:type phpinspect-splayt)
(declarations (phpinspect-make-splayt)
:type phpinspect-splayt
:documentation "The declaration tokens encountered.")
(imports (phpinspect-make-splayt)
:type phpinspect-splayt
:documentation "The import statements encountered.")
(functions (phpinspect-make-splayt)
:type phpinspect-splayt
:documentation "The function definitions encountered.")
(classes (phpinspect-make-splayt)
:type phpinspect-splayt
:documentation "The classes encountered.")
(class-variables (phpinspect-make-splayt)
:type phpinspect-splayt
:documentation "The class attribute variables encountered")
(namespaces (phpinspect-make-splayt)
:type phpinspect-splayt
:documentation "The namespaces encountered")
(-root-meta nil
:type phpinspect-meta)
(last-token-start nil
:type integer))
(define-inline phpinspect-bmap-root-meta (bmap)
(inline-letevals (bmap)
(inline-quote
(with-memoization (phpinspect-bmap--root-meta ,bmap)
(phpinspect-bmap-token-starting-at
,bmap (phpinspect-bmap-last-token-start ,bmap))))))
(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-overlaps-point reg1 (phpinspect-region-start reg2))
(phpinspect-region-overlaps-point reg1 (- (phpinspect-region-end reg2) 1))
(phpinspect-region-overlaps-point reg2 (phpinspect-region-start reg1))
(phpinspect-region-overlaps-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))))
(define-inline phpinspect-overlay-bmap (overlay)
(inline-quote (car (nthcdr 4 ,overlay))))
(define-inline phpinspect-overlay-delta (overlay)
(inline-quote (cadddr ,overlay)))
(define-inline phpinspect-overlay-start (overlay)
(inline-quote (cadr ,overlay)))
(define-inline phpinspect-overlay-end (overlay)
(inline-quote (caddr ,overlay)))
(define-inline phpinspect-overlay-overlaps-point (overlay point)
(inline-letevals (overlay point)
(inline-quote
(and (> (phpinspect-overlay-end ,overlay) ,point)
(<= (phpinspect-overlay-start ,overlay) ,point)))))
(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 (or overlay (phpinspect-make-meta nil start end whitespace-before token))))
(when (< end start)
(error "Token %s ends before it starts. Start: %s, end: %s" token start end))
(unless whitespace-before
(setq whitespace-before ""))
(puthash start token-meta starts)
(cond
((phpinspect-use-p (phpinspect-meta-token token-meta))
(phpinspect-splayt-insert
(phpinspect-bmap-imports bmap) (phpinspect-meta-start token-meta) token-meta))
((phpinspect-class-p (phpinspect-meta-token token-meta))
(phpinspect-splayt-insert
(phpinspect-bmap-classes bmap) (phpinspect-meta-start token-meta) token-meta))
((phpinspect-declaration-p (phpinspect-meta-token token-meta))
(phpinspect-splayt-insert
(phpinspect-bmap-declarations bmap) (phpinspect-meta-start token-meta) token-meta))
((phpinspect-function-p (phpinspect-meta-token token-meta))
(phpinspect-splayt-insert
(phpinspect-bmap-functions bmap) (phpinspect-meta-start token-meta) token-meta))
((phpinspect-namespace-p (phpinspect-meta-token token-meta))
(phpinspect-splayt-insert
(phpinspect-bmap-namespaces bmap) (phpinspect-meta-start token-meta) token-meta))
((or (phpinspect-const-p (phpinspect-meta-token token-meta))
(phpinspect-class-variable-p (phpinspect-meta-token token-meta)))
(phpinspect-splayt-insert
(phpinspect-bmap-class-variables bmap) (phpinspect-meta-start token-meta) token-meta)))
(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)))
(while (and (car stack) (>= (phpinspect-meta-start (car stack)) start))
(setq child (pop stack))
(phpinspect-meta-set-parent child token-meta))
(setf (phpinspect-bmap-token-stack bmap) stack)))
(setf (phpinspect-bmap-last-token-start bmap) start)
(push token-meta (phpinspect-bmap-token-stack bmap))))
(define-inline phpinspect-pctx-register-token (pctx token start end)
(inline-letevals (pctx)
(inline-quote
(phpinspect-bmap-register
(phpinspect-pctx-bmap ,pctx) ,start ,end ,token (phpinspect-pctx-consume-whitespace ,pctx)))))
(defsubst phpinspect-overlay-p (overlay)
(and (listp overlay)
(eq 'overlay (car overlay))))
(defsubst phpinspect-bmap-overlay-at-point (bmap point)
(let ((overlay (phpinspect-splayt-find-largest-before (phpinspect-bmap-overlays bmap) point)))
(when (and overlay (phpinspect-overlay-overlaps-point overlay point))
overlay)))
(cl-defmethod phpinspect-bmap-token-starting-at ((overlay (head overlay)) point)
(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)
(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-tokens-overlapping (bmap point)
(sort
(phpinspect-meta-find-overlapping-children (phpinspect-bmap-root-meta bmap) point)
#'phpinspect-meta-sort-width))
(defsubst phpinspect-overlay-encloses-meta (overlay meta)
(and (>= (phpinspect-meta-start meta) (phpinspect-overlay-start overlay))
(<= (phpinspect-meta-end meta) (phpinspect-overlay-end overlay))))
(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)
(unless (phpinspect-probably-token-p token)
(error "Unexpected argument, expected `phpinspect-token-p'. Got invalid token %s" token))
(or (gethash token (phpinspect-bmap-meta bmap))
(let ((found?))
(catch 'found
(phpinspect-splayt-traverse (overlay (phpinspect-bmap-overlays bmap))
(when (setq found? (phpinspect-bmap-token-meta overlay token))
;; Hit overlay's node to rebalance tree
(phpinspect-splayt-find
(phpinspect-bmap-overlays bmap) (phpinspect-overlay-end overlay))
(throw 'found found?)))))))
(cl-defmethod phpinspect-bmap-last-token-before-point ((bmap phpinspect-bmap) point)
"Search backward in BMAP for last token ending before POINT."
(phpinspect-meta-find-child-before-recursively (phpinspect-bmap-root-meta bmap) point))
(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
(last-overlay (phpinspect-splayt-node-value (phpinspect-splayt-root-node overlays))))
(phpinspect-meta-with-changeset token-meta
(phpinspect-meta-detach-parent token-meta)
(phpinspect-meta-shift token-meta pos-delta)
(if (and last-overlay (= (- start (length whitespace-before)) (phpinspect-overlay-end last-overlay))
(= pos-delta (phpinspect-overlay-delta last-overlay)))
(progn
(phpinspect--log "Expanding previous overlay from (%d,%d) to (%d,%d)"
(phpinspect-overlay-start last-overlay) (phpinspect-overlay-end last-overlay)
(phpinspect-overlay-start last-overlay) end)
(setf (phpinspect-overlay-end last-overlay) end)
(setf (phpinspect-meta-overlay token-meta) last-overlay))
(phpinspect--log "Inserting new overlay at (%d,%d)" start end)
(setq overlay `(overlay ,start ,end ,pos-delta ,bmap-overlay ,token-meta))
(setf (phpinspect-meta-overlay token-meta) overlay)
(phpinspect-splayt-insert (phpinspect-bmap-overlays bmap) (phpinspect-overlay-start overlay) overlay))
(phpinspect-bmap-register bmap start end (phpinspect-meta-token token-meta) whitespace-before token-meta))))
(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

@ -1,494 +0,0 @@
;;; phpinspect-buffer.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-class)
(require 'phpinspect-parser)
(require 'phpinspect-bmap)
(require 'phpinspect-edtrack)
(require 'phpinspect-index)
(require 'phpinspect-toc)
(require 'phpinspect-resolvecontext)
(require 'phpinspect-resolve)
(require 'phpinspect-util)
(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'.")
(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 associated emacs buffer")
(tree nil
:documentation
"Parsed token tree that resulted from last parse")
(map nil
:type phpinspect-bmap)
(-last-indexed-bmap nil)
(imports nil
:type phpinspect-toc)
(namespaces nil
:type phpinspect-toc)
(classes nil
:type phpinspect-toc)
(class-variables nil
:type phpinspect-toc)
(declarations nil
:type phpinspect-toc)
(functions nil
:type phpinspect-toc)
(token-index (make-hash-table :test 'eq :size 100 :rehash-size 1.5))
(-project nil
:type phpinspect-project)
(edit-tracker (phpinspect-make-edtrack)
:type phpinspect-edtrack))
(defun phpinspect-buffer-project (buffer)
(or (phpinspect-buffer--project buffer)
(with-current-buffer (phpinspect-buffer-buffer buffer)
(phpinspect--cache-get-project-create (phpinspect--get-or-create-global-cache)
(phpinspect-current-project-root)))))
(cl-defmethod phpinspect-buffer-parse ((buffer phpinspect-buffer) &optional no-interrupt)
"Parse the PHP code in the the emacs buffer that this object is
linked with."
(let ((tree))
(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
:interrupt-predicate (unless no-interrupt #'phpinspect--input-pending-p)
:bmap map
:incremental t
:previous-bmap buffer-map
:edtrack (phpinspect-buffer-edit-tracker buffer))))
(phpinspect-with-parse-context ctx
(phpinspect--log "Parsing buffer")
(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))
;; set return value
(setq tree parsed)))))
;; Else: Just return last parse result
(setq tree (phpinspect-buffer-tree buffer)))
tree))
(cl-defmethod phpinspect-buffer-get-index-for-token ((buffer phpinspect-buffer) token)
(gethash token (phpinspect-buffer-token-index buffer)))
(cl-defmethod phpinspect-buffer-set-index-reference-for-token ((buffer phpinspect-buffer) token index)
(unless (phpinspect-probably-token-p token)
(error "%s does not seem to be a token" token))
(puthash token index (phpinspect-buffer-token-index buffer)))
(cl-defmethod phpinspect-buffer-update-index-reference-for-token ((buffer phpinspect-buffer) old new)
(unless (and (phpinspect-probably-token-p old) (phpinspect-probably-token-p new))
(when (and old new)
(error "old and new parameters should be tokens")))
(when-let ((index (gethash old (phpinspect-buffer-token-index buffer))))
(remhash old (phpinspect-buffer-token-index buffer))
(puthash new index (phpinspect-buffer-token-index buffer))))
(cl-defmethod phpinspect-buffer-delete-index-for-token ((buffer phpinspect-buffer) token)
(unless (phpinspect-probably-token-p token)
(error "%s does not seem to be a token" token))
(cond ((phpinspect-class-p token)
(when-let ((class (gethash token (phpinspect-buffer-token-index buffer))))
(remhash token (phpinspect-buffer-token-index buffer))
(phpinspect-project-delete-class (phpinspect-buffer-project buffer) class)))
((or (phpinspect-const-p token) (phpinspect-variable-p token))
(when-let ((var (gethash token (phpinspect-buffer-token-index buffer))))
(remhash token (phpinspect-buffer-token-index buffer))
(when-let ((class (phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(car var))))
(phpinspect--class-delete-variable class (cdr var)))))
((phpinspect-function-p token)
(when-let ((func (gethash token (phpinspect-buffer-token-index buffer))))
(remhash token (phpinspect-buffer-token-index buffer))
(cond ((phpinspect-project-p (car func))
(phpinspect-project-delete-function (phpinspect-buffer-project buffer) (phpinspect--function-name-symbol (cdr func))))
((phpinspect--type-p (car func))
(when-let ((class (phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(car func))))
(phpinspect--class-delete-method class (cdr func))))
(t (error "Invalid index location")))))
(t (error "Cannot delete index for token %s" token))))
(cl-defmethod phpinspect-buffer-namespace-at-point ((buffer phpinspect-buffer) (point integer))
(let ((namespace (phpinspect-splayt-find-largest-before
(phpinspect-toc-tree (phpinspect-buffer-namespaces buffer))
point)))
(and namespace (phpinspect-meta-overlaps-point namespace point) namespace)))
(cl-defmethod phpinspect-buffer-index-imports ((buffer phpinspect-buffer) (imports (head phpinspect-splayt)))
(let (to-be-indexed)
(if (phpinspect-buffer-imports buffer)
(pcase-let* ((`(,new) (phpinspect-toc-update
(phpinspect-buffer-imports buffer) imports (phpinspect-buffer-root-meta buffer))))
(setq to-be-indexed new))
(setq to-be-indexed (phpinspect-splayt-to-list imports))
(setf (phpinspect-buffer-imports buffer) (phpinspect-make-toc imports)))
(phpinspect-project-enqueue-imports
(phpinspect-buffer-project buffer)
(phpinspect--uses-to-types (mapcar #'phpinspect-meta-token to-be-indexed)))))
(cl-defmethod phpinspect-buffer-index-namespaces ((buffer phpinspect-buffer) (namespaces (head phpinspect-splayt)))
(if (phpinspect-buffer-namespaces buffer)
(phpinspect-toc-update (phpinspect-buffer-namespaces buffer) namespaces (phpinspect-buffer-root-meta buffer))
(setf (phpinspect-buffer-namespaces buffer) (phpinspect-make-toc namespaces))))
(cl-defmethod phpinspect-buffer-index-declarations ((buffer phpinspect-buffer) (declarations (head phpinspect-splayt)))
(if (phpinspect-buffer-declarations buffer)
(phpinspect-toc-update (phpinspect-buffer-declarations buffer) declarations (phpinspect-buffer-root-meta buffer))
(setf (phpinspect-buffer-declarations buffer) (phpinspect-make-toc declarations))))
(defun phpinspect-get-token-index-context (namespaces all-imports meta)
(let ((namespace (phpinspect-toc-token-at-point namespaces (phpinspect-meta-start meta)))
namespace-name imports)
(if namespace
(progn
(setq namespace-name (phpinspect-namespace-name (phpinspect-meta-token namespace))
imports (mapcar #'phpinspect-meta-token
(phpinspect-toc-tokens-in-region
all-imports (phpinspect-meta-start namespace) (phpinspect-meta-start meta)))))
(setq namespace-name nil
imports (mapcar #'phpinspect-meta-token
(phpinspect-toc-tokens-in-region
all-imports 0 (phpinspect-meta-start meta)))))
(list imports namespace-name)))
(cl-defmethod phpinspect-buffer-index-classes ((buffer phpinspect-buffer) (classes (head phpinspect-splayt)))
(let ((declarations (phpinspect-buffer-declarations buffer))
(namespaces (phpinspect-buffer-namespaces buffer))
(buffer-imports (phpinspect-buffer-imports buffer))
(project (phpinspect-buffer-project buffer)))
(if (phpinspect-buffer-classes buffer)
(pcase-let* ((`(,new-classes ,deleted-classes) (phpinspect-toc-update
(phpinspect-buffer-classes buffer)
classes (phpinspect-buffer-root-meta buffer)))
(new-declarations) (declaration) (replaced) (indexed) (class))
(dolist (class new-classes)
(when (setq declaration (phpinspect-toc-token-at-or-after-point declarations (phpinspect-meta-start class)))
(push (cons (phpinspect-meta-token declaration) class) new-declarations)))
(dolist (deleted deleted-classes)
(if (and (setq class (phpinspect-buffer-get-index-for-token
buffer (phpinspect-meta-token deleted)))
(setq replaced (assoc (phpinspect--class-declaration class) new-declarations #'equal)))
(pcase-let ((`(,imports ,namespace-name) (phpinspect-get-token-index-context namespaces buffer-imports (cdr replaced))))
(phpinspect-buffer-update-index-reference-for-token
buffer (phpinspect-meta-token deleted) (phpinspect-meta-token (cdr replaced)))
(phpinspect--class-update-declaration class (car replaced) imports namespace-name)
(push (cdr replaced) indexed))
(phpinspect-buffer-delete-index-for-token buffer (phpinspect-meta-token deleted))))
(dolist (class new-declarations)
(unless (memq (cdr class) indexed)
(let (imports namespace-name class-name class-obj)
(pcase-setq `(,imports ,namespace-name) (phpinspect-get-token-index-context namespaces buffer-imports (cdr class))
`(,class-name) (phpinspect--index-class-declaration
(car class)
(phpinspect--make-type-resolver
(phpinspect--uses-to-types imports)
(phpinspect-class-block (phpinspect-meta-token (cdr class)))
namespace-name)))
(when class-name
(setq class-obj (phpinspect-project-get-class-create project class-name 'no-enqueue))
(phpinspect-buffer-set-index-reference-for-token buffer (phpinspect-meta-token (cdr class)) class-obj)
(phpinspect--class-update-declaration class-obj (car class) imports namespace-name))))))
;; Else: Index all classes
(setf (phpinspect-buffer-classes buffer) (phpinspect-make-toc classes))
(phpinspect-splayt-traverse (class classes)
(pcase-let* ((declaration (phpinspect-toc-token-at-or-after-point declarations (phpinspect-meta-start class)))
(`(,imports ,namespace-name) (phpinspect-get-token-index-context namespaces buffer-imports class))
(`(,class-name) (phpinspect--index-class-declaration
(phpinspect-meta-token declaration)
(phpinspect--make-type-resolver
(phpinspect--uses-to-types imports)
(phpinspect-class-block (phpinspect-meta-token class))
namespace-name)))
(class-obj))
(when class-name
(setq class-obj (phpinspect-project-get-class-create project class-name 'no-enqueue))
(phpinspect-buffer-set-index-reference-for-token buffer (phpinspect-meta-token class) class-obj)
(phpinspect--class-update-declaration class-obj (phpinspect-meta-token declaration) imports namespace-name)))))))
(cl-defmethod phpinspect-buffer-index-functions ((buffer phpinspect-buffer) (functions (head phpinspect-splayt)))
(let ((classes (phpinspect-buffer-classes buffer))
(namespaces (phpinspect-buffer-namespaces buffer))
(imports (phpinspect-buffer-imports buffer))
to-be-indexed class-environments class indexed)
(if (phpinspect-buffer-functions buffer)
(pcase-let ((`(,new-funcs ,deleted-funcs)
(phpinspect-toc-update (phpinspect-buffer-functions buffer) functions (phpinspect-buffer-root-meta buffer))))
(setq to-be-indexed new-funcs)
(dolist (deleted deleted-funcs)
(phpinspect-buffer-delete-index-for-token buffer (phpinspect-meta-token deleted))))
(setq to-be-indexed (phpinspect-splayt-to-list functions))
(setf (phpinspect-buffer-functions buffer) (phpinspect-make-toc functions)))
(dolist (func to-be-indexed)
(if (setq class (phpinspect-toc-token-at-point classes (phpinspect-meta-start func)))
(let (scope static indexed index-env comment-before)
(if (phpinspect-static-p (phpinspect-meta-token (phpinspect-meta-parent func)))
(progn
(setq static (phpinspect-meta-parent func))
(when (phpinspect-scope-p (phpinspect-meta-token (phpinspect-meta-parent static)))
(setq scope `(,(car (phpinspect-meta-token (phpinspect-meta-parent static)))
,(phpinspect-meta-token func))
comment-before (phpinspect-meta-find-left-sibling (phpinspect-meta-parent static)))))
(when (phpinspect-scope-p (phpinspect-meta-token (phpinspect-meta-parent func)))
(setq scope (phpinspect-meta-token (phpinspect-meta-parent func))
comment-before (phpinspect-meta-find-left-sibling (phpinspect-meta-parent func)))))
(unless scope (setq scope `(:public ,(phpinspect-meta-token func))))
(unless (setq index-env (alist-get class class-environments nil nil #'eq))
(setq index-env (phpinspect-get-token-index-context namespaces imports class))
(setcar index-env (phpinspect--uses-to-types (car index-env)))
(push (phpinspect--make-type-resolver (car index-env) (phpinspect-meta-token class) (cadr index-env))
index-env)
(push (cons class index-env) class-environments))
(unless comment-before
(setq comment-before (phpinspect-meta-find-left-sibling func)))
(setq comment-before (phpinspect-meta-token comment-before))
(pcase-let ((`(,type-resolver) index-env)
(class-obj (phpinspect-buffer-get-index-for-token buffer (phpinspect-meta-token class))))
(unless class-obj (error "Unable to find class obj for class %s" (phpinspect-meta-token class)))
(setq indexed (phpinspect--index-function-from-scope
type-resolver
scope
(and (phpinspect-comment-p comment-before) comment-before)))
(if static
(phpinspect--class-set-static-method class-obj indexed)
(phpinspect--class-set-method class-obj indexed))
(phpinspect-buffer-set-index-reference-for-token
buffer (phpinspect-meta-token func)
(cons (phpinspect--class-name class-obj) indexed))))
;; Else: index function
(pcase-let ((`(,imports ,namespace-name) (phpinspect-get-token-index-context namespaces imports func))
(comment-before (phpinspect-meta-find-left-sibling func)))
(setq indexed (phpinspect--index-function-from-scope
(phpinspect--make-type-resolver
(phpinspect--uses-to-types imports) nil namespace-name)
`(:public ,(phpinspect-meta-token func))
(and (phpinspect-comment-p comment-before) comment-before)
nil namespace-name))
(phpinspect-project-set-function (phpinspect-buffer-project buffer) indexed)
(phpinspect-buffer-set-index-reference-for-token
buffer (phpinspect-meta-token func)
(cons (phpinspect-buffer-project buffer) indexed)))))))
(cl-defmethod phpinspect-buffer-index-class-variables ((buffer phpinspect-buffer) (class-variables (head phpinspect-splayt)))
(let ((classes (phpinspect-buffer-classes buffer))
(namespaces (phpinspect-buffer-namespaces buffer))
(imports (phpinspect-buffer-imports buffer))
to-be-indexed class-environments class class-obj)
(if (phpinspect-buffer-class-variables buffer)
(pcase-let ((`(,new-vars ,deleted-vars)
(phpinspect-toc-update
(phpinspect-buffer-class-variables buffer) class-variables (phpinspect-buffer-root-meta buffer))))
(setq to-be-indexed new-vars)
(dolist (deleted deleted-vars)
(phpinspect-buffer-delete-index-for-token buffer (phpinspect-meta-token deleted))))
(setq to-be-indexed (phpinspect-splayt-to-list class-variables))
(setf (phpinspect-buffer-class-variables buffer) (phpinspect-make-toc class-variables)))
(dolist (var to-be-indexed)
(when (and class (> (phpinspect-meta-start var) (phpinspect-meta-end class)))
(setq class nil))
(unless class
(setq class (phpinspect-toc-token-at-point classes (phpinspect-meta-start var))))
(setq class-obj (phpinspect-buffer-get-index-for-token buffer (phpinspect-meta-token class)))
(let (scope static indexed index-env comment-before)
(if (phpinspect-static-p (phpinspect-meta-token (phpinspect-meta-parent var)))
(progn
(setq static (phpinspect-meta-parent var))
(when (phpinspect-scope-p (phpinspect-meta-token (phpinspect-meta-parent static)))
(setq scope `(,(car (phpinspect-meta-token (phpinspect-meta-parent static)))
,(phpinspect-meta-token var))
comment-before (phpinspect-meta-find-left-sibling (phpinspect-meta-parent static)))))
(when (phpinspect-scope-p (phpinspect-meta-token (phpinspect-meta-parent var)))
(setq scope (phpinspect-meta-token (phpinspect-meta-parent var))
comment-before (phpinspect-meta-find-left-sibling (phpinspect-meta-parent var)))))
(unless scope (setq scope `(:public ,(phpinspect-meta-token var))))
(unless (setq index-env (alist-get class class-environments nil nil #'eq))
(setq index-env (phpinspect-get-token-index-context namespaces imports class))
(push (cons class index-env) class-environments))
(unless comment-before
(setq comment-before (phpinspect-meta-find-left-sibling var)))
(setq comment-before (phpinspect-meta-token comment-before))
(pcase-let* ((`(,imports ,namespace-name) index-env)
(type-resolver
(phpinspect--make-type-resolver
(phpinspect--uses-to-types imports)
(phpinspect-meta-token class)
namespace-name)))
(setq indexed
(if (phpinspect-const-p (phpinspect-meta-token var))
(phpinspect--index-const-from-scope scope)
(phpinspect--index-variable-from-scope
type-resolver
scope
(and (phpinspect-comment-p comment-before) comment-before)
static)))
(when (and (phpinspect-variable-p (phpinspect-meta-token var)) (not (phpinspect--variable-type indexed)))
(when-let* ((constructor (phpinspect--class-get-method class-obj (phpinspect-intern-name "__construct")))
(rctx (phpinspect--make-resolvecontext :enclosing-tokens (list (phpinspect-meta-token class))
:enclosing-metadata (list class))))
(setf (phpinspect--variable-type indexed)
(phpinspect-get-pattern-type-in-block
rctx (phpinspect--make-pattern :m `(:variable "this")
:m `(:object-attrib (:word ,(cadr (phpinspect-meta-token var)))))
(phpinspect-function-block (phpinspect--function-token constructor))
type-resolver
(phpinspect-function-argument-list (phpinspect--function-token constructor))))))
(phpinspect--class-set-variable class-obj indexed)
(phpinspect-buffer-set-index-reference-for-token
buffer (phpinspect-meta-token var)
(cons (phpinspect--class-name class-obj) indexed)))))))
(cl-defmethod phpinspect-buffer-reset ((buffer phpinspect-buffer))
(setf (phpinspect-buffer-tree buffer) nil)
(setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap))
(setf (phpinspect-buffer-declarations buffer) nil)
(setf (phpinspect-buffer-imports buffer) nil)
(setf (phpinspect-buffer-token-index buffer)
(make-hash-table :test 'eq :size 100 :rehash-size 1.5))
(phpinspect-edtrack-clear (phpinspect-buffer-edit-tracker buffer)))
(cl-defmethod phpinspect-buffer-reparse ((buffer phpinspect-buffer))
(phpinspect-buffer-reset buffer)
(phpinspect-buffer-parse buffer 'no-interrupt))
(cl-defmethod phpinspect-buffer-update-project-index ((buffer phpinspect-buffer))
(when (phpinspect-buffer-project buffer)
(let ((map (phpinspect-buffer-map buffer)))
(unless (eq map (phpinspect-buffer--last-indexed-bmap buffer))
(phpinspect-buffer-index-imports buffer (phpinspect-bmap-imports map))
(phpinspect-buffer-index-declarations buffer (phpinspect-bmap-declarations map))
(phpinspect-buffer-index-namespaces buffer (phpinspect-bmap-namespaces map))
(phpinspect-buffer-index-classes buffer (phpinspect-bmap-classes map))
(phpinspect-buffer-index-functions buffer (phpinspect-bmap-functions map))
(phpinspect-buffer-index-class-variables buffer (phpinspect-bmap-class-variables map))
(setf (phpinspect-buffer--last-indexed-bmap buffer) map)))))
(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))
"Mark a region of the buffer as edited."
;; Take into account "atoms" (tokens without clear delimiters like words,
;; variables and object attributes. The meaning of these tokens will change as
;; they grow or shrink, so their full regions need to be marked for a reparse).
(save-excursion
(goto-char start)
(when (looking-back "\\(\\$\\|->\\|::\\)?[^][)(}{[:blank:]\n;'\"]+" nil t)
(setq start (- start (length (match-string 0))))
(setq pre-change-length (+ pre-change-length (length (match-string 0))))))
(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)
(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-resolver ((buffer phpinspect-buffer))
"Derive location resolver from BUFFER's buffer map. Guarantees to
retrieve the lastest available map of BUFFER upon first
invocation, but subsequent invocations will not update the used
map afterwards, so don't keep the resolver around for long term
use."
(let ((bmap-resolver))
(lambda (token)
(funcall (with-memoization bmap-resolver
(phpinspect-bmap-make-location-resolver (phpinspect-buffer-map buffer)))
token))))
(cl-defmethod phpinspect-buffer-root-meta ((buffer phpinspect-buffer))
(phpinspect-bmap-root-meta (phpinspect-buffer-map buffer)))
(defun phpinspect-display-buffer-tree ()
(interactive)
(when phpinspect-current-buffer
(let ((buffer phpinspect-current-buffer))
(pop-to-buffer (generate-new-buffer "phpinspect-buffer-tree"))
(insert (pp-to-string (phpinspect-buffer-parse buffer 'no-interrupt)))
(read-only-mode))))
(defun phpinspect-display-buffer-index ()
(interactive)
(when phpinspect-current-buffer
(let ((buffer phpinspect-current-buffer))
(pop-to-buffer (generate-new-buffer "phpinspect-buffer-tree"))
(insert (pp-to-string (phpinspect--index-tokens (phpinspect-buffer-parse buffer 'no-interrupt))))
(read-only-mode))))
(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)))
(provide 'phpinspect-buffer)

@ -1,290 +0,0 @@
;;; phpinspect.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-project)
(require 'phpinspect-autoload)
(require 'phpinspect-worker)
(defcustom phpinspect-load-stubs t
"If and when phpinspect should load code stubs."
:type '(choice
(const
:tag
"Load stubs on first mode init." t)
(const
:tag
"Never load stubs." nil))
:group 'phpinspect)
(defvar phpinspect-buffers (make-hash-table :test #'eq)
"All buffers for which `phpinspect-mode' is currently active.
Hash table with buffer (native emacs buffer object, `bufferp') as
key, and a reset-function as value. The reset-function is called
without arguments when the cache is purged (see
`phpinspect-purge-cache'.")
(defun phpinspect-register-current-buffer (reset-func)
(puthash (current-buffer) reset-func phpinspect-buffers))
(defun phpinspect-unregister-current-buffer ()
(remhash (current-buffer) phpinspect-buffers))
(defvar phpinspect-stub-cache nil
"An instance of `phpinspect--cache' containing an index of PHP
functions and classes which phpinspect preloads. This index is
not supposed to be mutated after initial creation.")
(defmacro phpinspect--cache-edit (cache &rest body)
(declare (indent 1))
`(unless (phpinspect--cache-read-only-p ,cache)
,@body))
(defvar phpinspect-cache nil
"An object used to store and access metadata of PHP projects.")
(cl-defstruct (phpinspect--cache (:constructor phpinspect--make-cache))
(read-only-p nil
:type boolean
:documentation
"Whether this cache instance is read-only, meaning that it's data
should never be changed.
When the value of this slot is non-nil:
- Actions that would normally mutate it's data should become
no-ops.
- All projects that are retrieved from it should be marked as read-only as well.")
(extra-class-retriever nil
:type lambda
:documentation
"A function that should accept a `phpinspect--type' and return
matching `phpinspect--class' instances or nil. Used to discover
classes that are defined outside of code that this cache knows about.")
(extra-function-retriever nil
:type lambda
:documentation
"A function that should accept a `phpinspect-name' (see
`phpinspect-intern-name') and return matching
`phpinspect--function' instances or nil. Used to discover
functions that are defined outside of code that this cache knows
about.")
(projects (make-hash-table :test 'equal :size 10)
:type hash-table
:documentation
"A `hash-table` with the root directories of projects
as keys and project caches as values."))
(defun phpinspect--get-stub-class (fqn)
(when phpinspect-stub-cache
(phpinspect--log "Getting stub class")
(catch 'return
(maphash (lambda (_name project)
(when-let ((class (phpinspect-project-get-class project fqn)))
(throw 'return class)))
(phpinspect--cache-projects phpinspect-stub-cache)))))
(defun phpinspect--get-stub-function (name)
(when phpinspect-stub-cache
(if name
(catch 'return
(phpinspect--log "Getting stub function by name %s" name)
(maphash (lambda (_name project)
(when-let ((class (phpinspect-project-get-function project name)))
(throw 'return class)))
(phpinspect--cache-projects phpinspect-stub-cache)))
(let* ((funcs (cons nil nil))
(funcs-rear funcs))
(phpinspect--log "Retrieving all stub functions for nil name")
(maphash (lambda (_name project)
(setq funcs-rear (last (nconc funcs-rear (phpinspect-project-get-functions project)))))
(phpinspect--cache-projects phpinspect-stub-cache))
(cdr funcs)))))
(defun phpinspect--get-or-create-global-cache ()
"Get `phpinspect-cache'.
If its value is nil, it is created and then returned."
(or phpinspect-cache
(setq phpinspect-cache
(phpinspect--make-cache
:extra-class-retriever #'phpinspect--get-stub-class
:extra-function-retriever #'phpinspect--get-stub-function))))
(defun phpinspect-purge-cache ()
"Assign a fresh, empty cache object to `phpinspect-cache'.
This effectively purges any cached code information from all
currently opened projects."
(interactive)
(when phpinspect-cache
;; Allow currently known cached projects to cleanup after themselves
(maphash (lambda (_ project)
(phpinspect-project-purge project))
(phpinspect--cache-projects phpinspect-cache)))
(maphash (lambda (buffer reset-hook)
(with-current-buffer buffer
(funcall reset-hook)))
phpinspect-buffers)
;; Assign a fresh cache object
(setq phpinspect-cache (phpinspect--get-or-create-global-cache))
(setq phpinspect-names (phpinspect-make-name-hash))
(phpinspect-define-standard-types))
(cl-defmethod phpinspect--cache-get-project
((cache phpinspect--cache) (project-root string))
(let ((project (gethash project-root (phpinspect--cache-projects cache))))
(when (and project (phpinspect--cache-read-only-p cache)
(not (phpinspect-project-read-only-p project)))
(setf (phpinspect-project-read-only-p project) t))
project))
(defun phpinspect-get-or-create-cached-project-class (project-root class-fqn)
(when project-root
(let ((project (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
project-root)))
(phpinspect-project-get-class-extra-or-create project class-fqn))))
(cl-defmethod phpinspect--cache-get-project-create
((cache phpinspect--cache) (project-root string))
"Get a project that is located in PROJECT-ROOT from CACHE.
If no such project exists in the cache yet, it is created and
then returned."
(let ((project (phpinspect--cache-get-project cache project-root)))
(unless project
(phpinspect--cache-edit cache
(setq project
(puthash project-root
(phpinspect--make-project
:fs (phpinspect-make-fs)
:root project-root
:extra-class-retriever (phpinspect--cache-extra-class-retriever cache)
:extra-function-retriever (phpinspect--cache-extra-function-retriever cache)
:worker (phpinspect-make-dynamic-worker))
(phpinspect--cache-projects cache)))
(let ((autoloader (phpinspect-make-autoloader
:fs (phpinspect-project-fs project)
:file-indexer (phpinspect-project-make-file-indexer project)
:project-root-resolver (phpinspect-project-make-root-resolver project))))
(setf (phpinspect-project-autoload project) autoloader)
(phpinspect-autoloader-refresh autoloader)
(phpinspect-project-enqueue-include-dirs project))))
project))
(defun phpinspect-project-enqueue-include-dirs (project)
(interactive (list (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect-current-project-root))))
(phpinspect-project-edit project
(let ((dirs (alist-get 'include-dirs
(alist-get (phpinspect-project-root project)
phpinspect-projects
nil nil #'string=))))
(dolist (dir dirs)
(phpinspect-message "enqueueing dir %s" dir)
(phpinspect-worker-enqueue
(phpinspect-project-worker project)
(phpinspect-make-index-dir-task :dir dir :project project))))))
(defun phpinspect-project-add-include-dir (dir)
"Configure DIR as an include dir for the current project."
(interactive (list (read-directory-name "Include Directory: ")))
(custom-set-variables '(phpinspect-projects))
(let ((existing
(alist-get (phpinspect-current-project-root) phpinspect-projects nil #'string=)))
(if existing
(push dir (alist-get 'include-dirs existing))
(push `(,(phpinspect-current-project-root) . ((include-dirs . (,dir)))) phpinspect-projects)))
(customize-save-variable 'phpinspect-projects phpinspect-projects)
(phpinspect-project-enqueue-include-dirs (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect-current-project-root))))
(defconst phpinspect-stub-directory
(expand-file-name "stubs" (file-name-directory (macroexp-file-name)))
"Directory where PHP stub files are located.")
(defconst phpinspect-data-directory
(expand-file-name "data" (file-name-directory (macroexp-file-name)))
"Directory for data distributed with phpinspect.")
(defconst phpinspect-stub-cache-file
(expand-file-name "builtin-stubs.eld" phpinspect-data-directory)
"")
(defconst phpinspect-builtin-index-file
(expand-file-name (concat "builtin-stubs-index.eld" (if (zlib-available-p) ".gz" ""))
phpinspect-data-directory)
"")
(defun phpinspect-build-stub-cache ()
(let* ((cache (phpinspect--make-cache))
(builtin-project (phpinspect--cache-get-project-create cache "builtins"))
(phpinspect-worker 'nil-worker))
(phpinspect-project-add-index builtin-project (phpinspect-build-stub-index))))
(defun phpinspect-build-stub-index ()
(phpinspect--index-tokens (phpinspect-parse-file (expand-file-name "builtins.php" phpinspect-stub-directory))))
(defun phpinspect-dump-stub-index ()
(interactive)
(let* ((phpinspect-names (phpinspect-make-name-hash))
(index (phpinspect-build-stub-index)))
(with-temp-buffer
(let ((print-length nil)
(print-level nil)
(print-circle t))
(prin1 (list (cons 'names phpinspect-names)
(cons 'index index))
(current-buffer))
(write-file phpinspect-builtin-index-file)))))
(defun phpinspect-load-stub-index ()
(interactive)
(unless (file-exists-p phpinspect-builtin-index-file)
(phpinspect-message "No stub index dump found, dumping stub index ...")
(phpinspect-dump-stub-index))
(let* ((data (with-temp-buffer
(insert-file-contents phpinspect-builtin-index-file)
(goto-char (point-min))
(read (current-buffer))))
(project (phpinspect--make-project :worker 'nil-worker)))
(phpinspect-purge-cache)
(setq phpinspect-names (alist-get 'names data))
(phpinspect-define-standard-types)
(setq phpinspect-stub-cache (phpinspect--make-cache))
(phpinspect-project-add-index project (alist-get 'index data))
(puthash "builtins" project (phpinspect--cache-projects phpinspect-stub-cache))
(setf (phpinspect--cache-read-only-p phpinspect-stub-cache) t)))
;;; phpinspect.el ends here
(provide 'phpinspect-cache)

@ -1,70 +0,0 @@
;;; phpinspect-changeset.el --- Metadata changeset module -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
(eval-when-compile
(require 'phpinspect-meta))
(define-inline phpinspect-make-changeset (meta)
(inline-letevals (meta)
(inline-quote
(list (phpinspect-meta-start ,meta) (phpinspect-meta-end ,meta)
(phpinspect-meta-parent ,meta) (phpinspect-meta-overlay ,meta)
(phpinspect-meta-parent-offset ,meta) ,meta))))
(define-inline phpinspect-changeset-start (set)
(inline-quote (car ,set)))
(define-inline phpinspect-changeset-end (set)
(inline-quote (cadr ,set)))
(define-inline phpinspect-changeset-parent (set)
(inline-quote (caddr ,set)))
(define-inline phpinspect-changeset-overlay (set)
(inline-quote (cadddr ,set)))
(define-inline phpinspect-changeset-parent-offset (set)
(inline-quote (car (cddddr ,set))))
(define-inline phpinspect-changeset-meta (set)
(inline-quote (car (nthcdr 5 ,set))))
(define-inline phpinspect-changeset-revert (changeset)
(inline-letevals (changeset)
(inline-quote
(progn
(setf (phpinspect-meta-parent (phpinspect-changeset-meta ,changeset))
(phpinspect-changeset-parent ,changeset))
(setf (phpinspect-meta-overlay (phpinspect-changeset-meta ,changeset))
(phpinspect-changeset-overlay ,changeset))
(setf (phpinspect-meta-absolute-start (phpinspect-changeset-meta ,changeset))
(phpinspect-changeset-start ,changeset))
(setf (phpinspect-meta-absolute-end (phpinspect-changeset-meta ,changeset))
(phpinspect-changeset-end ,changeset))
(setf (phpinspect-meta-parent-offset (phpinspect-changeset-meta ,changeset))
(phpinspect-changeset-parent-offset ,changeset))))))
(provide 'phpinspect-changeset)
;;; phpinspect-changeset.el ends here

@ -1,78 +0,0 @@
;;; phpinspect-class-struct.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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--class (:constructor phpinspect--make-class-generated))
(class-retriever nil
:type lambda
:documentaton
"A function that returns classes for types
(should accept `phpinspect--type' as argument)")
(read-only-p nil
:type boolean
:documentation
"Whether this class instance is read-only, meaning that its data
should never be changed. Methods and functions that are meant to
manipulate class data should become no-ops when this slot has a
non-nil value.")
(index nil
:type phpinspect--indexed-class
:documentation
"The index that this class is derived from")
(methods (make-hash-table :test 'eq :size 20 :rehash-size 20)
:type hash-table
:documentation
"All methods, including those from extended classes.")
(static-methods (make-hash-table :test 'eq :size 20 :rehash-size 20)
:type hash-table
:documentation
"All static methods this class provides,
including those from extended classes.")
(name nil
:type phpinspect--type)
(variables nil
:type list
:documentation
"Variables that belong to this class.")
(extended-classes nil
:type list
:documentation
"All extended/implemented classes.")
(subscriptions (make-hash-table :test #'eq :size 10 :rehash-size 1.5)
:type hash-table
:documentation
"A list of subscription functions that should be
called whenever anything about this class is
updated")
(declaration nil)
(initial-index nil
:type bool
:documentation
"A boolean indicating whether or not this class
has been indexed yet."))
(provide 'phpinspect-class-struct)

@ -1,263 +0,0 @@
;;; phpinspect-class.el --- PHP parsing module -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-type)
(require 'phpinspect-class-struct)
(defmacro phpinspect--class-edit (class &rest body)
"Declare intent to edit CLASS in BODY.
Conditionally executes BODY depending on
`phpinspect--class-read-only-p' value."
(declare (indent 1))
`(unless (phpinspect--class-read-only-p ,class)
,@body))
(cl-defmethod phpinspect--class-trigger-update ((class phpinspect--class))
(dolist (sub (hash-table-values (phpinspect--class-subscriptions class)))
(funcall sub class)))
(cl-defmethod phpinspect--class-update-extensions ((class phpinspect--class) extensions)
(phpinspect--class-edit class
(setf (phpinspect--class-extended-classes class)
(seq-filter
#'phpinspect--class-p
(mapcar
(lambda (class-name)
(funcall (phpinspect--class-class-retriever class) class-name))
extensions)))
(dolist (extended (phpinspect--class-extended-classes class))
(phpinspect--class-incorporate class extended))))
(cl-defmethod phpinspect--class-set-index ((class phpinspect--class)
(index (head phpinspect--indexed-class)))
(phpinspect--class-edit class
(setf (phpinspect--class-declaration class) (alist-get 'declaration index))
(setf (phpinspect--class-name class) (alist-get 'class-name index))
;; Override methods when class seems syntactically correct (has balanced braces)
(when (alist-get 'complete index)
(let ((methods (phpinspect--class-methods class))
(static-methods (phpinspect--class-static-methods class)))
(dolist (method (hash-table-values methods))
(unless (phpinspect--function--inherited method)
(remhash (phpinspect--function-name-symbol method) methods)))
(dolist (method (hash-table-values static-methods))
(unless (phpinspect--function--inherited method)
(remhash (phpinspect--function-name-symbol method) static-methods)))))
(setf (phpinspect--class-initial-index class) t)
(setf (phpinspect--class-index class) index)
(dolist (method (alist-get 'methods index))
(phpinspect--class-update-method class method))
(dolist (method (alist-get 'static-methods index))
(phpinspect--class-update-static-method class method))
(setf (phpinspect--class-variables class)
(append (alist-get 'variables index)
(alist-get 'constants index)
(alist-get 'static-variables index)))
(phpinspect--class-update-extensions
class `(,@(alist-get 'implements index) ,@(alist-get 'extends index)))
(phpinspect--class-trigger-update class)))
(cl-defmethod phpinspect--class-update-declaration
((class phpinspect--class) declaration imports namespace-name)
(phpinspect--class-edit class
(pcase-let ((`(,class-name ,extends ,implements ,_used-types)
(phpinspect--index-class-declaration
declaration (phpinspect--make-type-resolver
(phpinspect--uses-to-types imports) nil namespace-name))))
(setf (phpinspect--class-name class) class-name)
(setf (phpinspect--class-declaration class) declaration)
(phpinspect--class-update-extensions class `(,@extends ,@implements)))))
(cl-defmethod phpinspect--class-get-method ((class phpinspect--class) (method-name (head phpinspect-name)))
(gethash method-name (phpinspect--class-methods class)))
(cl-defmethod phpinspect--class-get-static-method ((class phpinspect--class) (method-name (head phpinspect-name)))
(gethash method-name (phpinspect--class-static-methods class)))
(cl-defmethod phpinspect--class-get-variable
((class phpinspect--class) (variable-name string))
(catch 'found
(dolist (variable (phpinspect--class-variables class))
(when (string= variable-name (phpinspect--variable-name variable))
(throw 'found variable)))))
(cl-defmethod phpinspect--class-set-variable ((class phpinspect--class)
(var phpinspect--variable))
(phpinspect--class-edit class
(push var (phpinspect--class-variables class))))
(cl-defmethod phpinspect--class-delete-variable ((class phpinspect--class)
(var phpinspect--variable))
(phpinspect--class-edit class
(setf (phpinspect--class-variables class)
(seq-filter (lambda (clvar) (not (eq var clvar)))
(phpinspect--class-variables class)))))
(cl-defmethod phpinspect--class-get-variables ((class phpinspect--class))
(seq-filter #'phpinspect--variable-vanilla-p (phpinspect--class-variables class)))
(cl-defmethod phpinspect--class-get-static-variables ((class phpinspect--class))
(seq-filter #'phpinspect--variable-static-p (phpinspect--class-variables class)))
(cl-defmethod phpinspect--class-get-constants ((class phpinspect--class))
(seq-filter #'phpinspect--variable-const-p (phpinspect--class-variables class)))
(cl-defmethod phpinspect--add-method-copy-to-map
((map hash-table)
(class-name phpinspect--type)
(method phpinspect--function))
(setq method (phpinspect--copy-function method))
(setf (phpinspect--function-return-type method)
(phpinspect--resolve-late-static-binding
(phpinspect--function-return-type method)
class-name))
(puthash (phpinspect--function-name-symbol method)
method
map))
(cl-defmethod phpinspect--class-set-method ((class phpinspect--class)
(method phpinspect--function))
(phpinspect--class-edit class
(phpinspect--log "Adding method by name %s to class"
(phpinspect--function-name method))
(phpinspect--add-method-copy-to-map
(phpinspect--class-methods class)
(phpinspect--class-name class)
method)))
(cl-defmethod phpinspect--class-set-static-method ((class phpinspect--class)
(method phpinspect--function))
(phpinspect--class-edit class
(phpinspect--add-method-copy-to-map
(phpinspect--class-static-methods class)
(phpinspect--class-name class)
method)))
(cl-defmethod phpinspect--class-delete-method ((class phpinspect--class) (method phpinspect--function))
(phpinspect--class-edit class
(remhash (phpinspect--function-name-symbol method) (phpinspect--class-static-methods class))
(remhash (phpinspect--function-name-symbol method) (phpinspect--class-methods class))))
(cl-defmethod phpinspect--class-get-method-return-type
((class phpinspect--class) (method-name (head phpinspect-name)))
(let ((method (phpinspect--class-get-method class method-name)))
(when method
(phpinspect--function-return-type method))))
(cl-defmethod phpinspect--class-get-static-method-return-type
((class phpinspect--class) (method-name (head phpinspect-name)))
(let ((method (phpinspect--class-get-static-method class method-name)))
(when method
(phpinspect--function-return-type method))))
(cl-defmethod phpinspect--class-get-method-list ((class phpinspect--class))
(hash-table-values (phpinspect--class-methods class)))
(cl-defmethod phpinspect--class-get-static-method-list ((class phpinspect--class))
(hash-table-values (phpinspect--class-static-methods class)))
(cl-defmethod phpinspect--merge-method ((class-name phpinspect--type)
(existing phpinspect--function)
(method phpinspect--function)
&optional extended)
(let ((new-return-type (phpinspect--resolve-late-static-binding
(phpinspect--function-return-type method)
class-name)))
(unless (phpinspect--type= new-return-type phpinspect--null-type)
(phpinspect--log "method return type %s" (phpinspect--function-return-type method))
(setf (phpinspect--function-return-type existing)
new-return-type))
(setf (phpinspect--function--inherited existing)
extended)
(setf (phpinspect--function-arguments existing)
(phpinspect--function-arguments method)))
existing)
(cl-defmethod phpinspect--class-update-static-method ((class phpinspect--class)
(method phpinspect--function)
&optional extended)
(phpinspect--class-edit class
(let ((existing (gethash (phpinspect--function-name-symbol method)
(phpinspect--class-static-methods class))))
(if existing
(phpinspect--merge-method (phpinspect--class-name class) existing method extended)
(setf (phpinspect--function--inherited method) extended)
(phpinspect--class-set-static-method class method)))))
(cl-defmethod phpinspect--class-update-method ((class phpinspect--class)
(method phpinspect--function)
&optional extended)
(phpinspect--class-edit class
(let* ((existing (gethash (phpinspect--function-name-symbol method)
(phpinspect--class-methods class))))
(if existing
(phpinspect--merge-method (phpinspect--class-name class) existing method extended)
(setf (phpinspect--function--inherited method) extended)
(phpinspect--class-set-method class method)))))
;; FIXME: Remove inherited methods when they no longer exist in parent classes
;; (and/or the current class in the case of abstract methods).
(cl-defmethod phpinspect--class-incorporate ((class phpinspect--class)
(other-class phpinspect--class))
(phpinspect--class-edit class
(dolist (method (phpinspect--class-get-method-list other-class))
(phpinspect--class-update-method class method 'extended))
(dolist (method (phpinspect--class-get-static-method-list other-class))
(phpinspect--class-update-static-method class method 'extended))
(phpinspect--class-subscribe class other-class)))
(cl-defmethod phpinspect--class-subscribe ((class phpinspect--class)
(subscription-class phpinspect--class))
(phpinspect--class-edit class
(unless (gethash subscription-class (phpinspect--class-subscriptions class))
(let ((update-function
(lambda (new-class)
(phpinspect--class-edit class
(phpinspect--class-incorporate class new-class)
(phpinspect--class-trigger-update class)))))
(puthash subscription-class update-function
(phpinspect--class-subscriptions subscription-class))))))
(provide 'phpinspect-class)
;;; phpinspect-class.el ends here

@ -1,319 +0,0 @@
;;; phpinspect-type.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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 'obarray)
(require 'phpinspect-bmap)
(require 'phpinspect-buffer)
(require 'phpinspect-resolvecontext)
(require 'phpinspect-suggest)
(defvar phpinspect--last-completion-list nil
"Used internally to save metadata about completion options
between company backend calls")
(cl-defstruct (phpinspect--completion
(:constructor phpinspect--construct-completion))
"Contains a possible completion value with all it's attributes."
(target nil
:documentation
"The object that this completion is aimed at/completing towards")
(value nil :type string)
(meta nil :type string)
(annotation nil :type string)
(kind nil :type symbol))
(cl-defgeneric phpinspect--make-completion (completion-candidate)
"Creates a `phpinspect--completion` for a possible completion
candidate. Candidates can be indexed functions and variables.")
(cl-defstruct (phpinspect--completion-list
(:constructor phpinspect--make-completion-list))
"Contains all data for a completion at point"
(completion-start nil
:type integer)
(completion-end nil
:type integer)
(completions (obarray-make)
:type obarray
:documentation
"A list of completion strings")
(has-candidates nil))
(cl-defgeneric phpinspect--completion-list-add
(comp-list completion)
"Add a completion to a completion-list.")
(cl-defmethod phpinspect--completion-list-add
((comp-list phpinspect--completion-list) (completion phpinspect--completion))
(setf (phpinspect--completion-list-has-candidates comp-list) t)
;; Ignore completions in an invalid state (nil values)
(when (phpinspect--completion-value completion)
(unless (intern-soft (phpinspect--completion-value completion)
(phpinspect--completion-list-completions comp-list))
(set (intern (phpinspect--completion-value completion)
(phpinspect--completion-list-completions comp-list))
completion))))
(cl-defmethod phpinspect--completion-list-get-metadata
((comp-list phpinspect--completion-list) (completion-name string))
(let ((comp-sym (intern-soft completion-name
(phpinspect--completion-list-completions comp-list))))
(when comp-sym
(symbol-value comp-sym))))
(cl-defmethod phpinspect--completion-list-strings
((comp-list phpinspect--completion-list))
(let ((strings))
(obarray-map (lambda (sym) (push (symbol-name sym) strings))
(phpinspect--completion-list-completions comp-list))
strings))
(cl-defstruct (phpinspect-completion-query (:constructor phpinspect-make-completion-query))
(completion-point 0
:type integer
:documentation
"Position in the buffer from where the resolvecontext is determined.")
(point 0
:type integer
:documentation "Position in buffer for which to provide completions")
(buffer nil
:type phpinspect-buffer))
(cl-defgeneric phpinspect-comp-strategy-supports (strategy query context)
"Should return non-nil if STRATEGY should be deployed for QUERY
and CONTEXT. All strategies must implement this method.")
(cl-defgeneric phpinspect-comp-strategy-execute (strategy query context)
"Should return a list of objects for which
`phpinspect--make-completion' is implemented.")
(cl-defstruct (phpinspect-comp-sigil (:constructor phpinspect-make-comp-sigil))
"Completion strategy for the sigil ($) character.")
(defun phpinspect-completion-subject-at-point (buffer point predicate)
(let ((subject (phpinspect-bmap-last-token-before-point
(phpinspect-buffer-parse-map buffer) point)))
(and subject
(funcall predicate (phpinspect-meta-token subject))
(>= (phpinspect-meta-end subject) point)
subject)))
(cl-defmethod phpinspect-comp-strategy-supports
((_strat phpinspect-comp-sigil) (q phpinspect-completion-query)
(_rctx phpinspect--resolvecontext))
(when-let ((subject (phpinspect-completion-subject-at-point
(phpinspect-completion-query-buffer q)
(phpinspect-completion-query-point q)
#'phpinspect-variable-p)))
(list (+ (phpinspect-meta-start subject) 1) (phpinspect-meta-end subject))))
(cl-defmethod phpinspect-comp-strategy-execute
((_strat phpinspect-comp-sigil) (_q phpinspect-completion-query)
(rctx phpinspect--resolvecontext))
(phpinspect-suggest-variables-at-point rctx))
(define-inline phpinspect-attrib-start (attrib-meta)
"The start position of the name of the attribute that is being referenced.
ATTRIB-META must be an instance of phpinspect-meta (see `phpinspect-make-meta'),
belonging to a token that conforms with `phpinspect-attrib-p'"
(inline-letevals (attrib-meta)
(inline-quote
(- (phpinspect-meta-end ,attrib-meta)
(length (cadadr (phpinspect-meta-token ,attrib-meta)))))))
(cl-defstruct (phpinspect-comp-attribute (:constructor phpinspect-make-comp-attribute))
"Completion strategy for object attributes")
(cl-defmethod phpinspect-comp-strategy-supports
((_strat phpinspect-comp-attribute) (q phpinspect-completion-query)
(_rctx phpinspect--resolvecontext))
(when-let ((subject (phpinspect-completion-subject-at-point
(phpinspect-completion-query-buffer q)
(phpinspect-completion-query-point q)
#'phpinspect-object-attrib-p)))
(list (phpinspect-attrib-start subject) (phpinspect-meta-end subject))))
(cl-defmethod phpinspect-comp-strategy-execute
((_strat phpinspect-comp-attribute) (_q phpinspect-completion-query)
(rctx phpinspect--resolvecontext))
(phpinspect-suggest-attributes-at-point rctx))
(cl-defstruct (phpinspect-comp-static-attribute (:constructor phpinspect-make-comp-static-attribute))
"Completion strategy for static attributes")
(cl-defmethod phpinspect-comp-strategy-supports
((_strat phpinspect-comp-static-attribute) (q phpinspect-completion-query)
(_rctx phpinspect--resolvecontext))
(when-let ((subject (phpinspect-completion-subject-at-point
(phpinspect-completion-query-buffer q)
(phpinspect-completion-query-point q)
#'phpinspect-static-attrib-p)))
(list (phpinspect-attrib-start subject) (phpinspect-meta-end subject))))
(cl-defmethod phpinspect-comp-strategy-execute
((_strat phpinspect-comp-static-attribute) (_q phpinspect-completion-query)
(rctx phpinspect--resolvecontext))
(phpinspect-suggest-attributes-at-point rctx 'static))
(cl-defstruct (phpinspect-comp-word (:constructor phpinspect-make-comp-word))
"Comletion strategy for bare words")
(cl-defmethod phpinspect-comp-strategy-supports
((_strat phpinspect-comp-word) (q phpinspect-completion-query)
(_rctx phpinspect--resolvecontext))
(when-let ((subject (phpinspect-completion-subject-at-point
(phpinspect-completion-query-buffer q)
(phpinspect-completion-query-point q)
#'phpinspect-word-p)))
(list (phpinspect-meta-start subject) (phpinspect-meta-end subject))))
(cl-defmethod phpinspect-comp-strategy-execute
((_strat phpinspect-comp-word) (_q phpinspect-completion-query)
(rctx phpinspect--resolvecontext))
(phpinspect-suggest-functions rctx))
(defvar phpinspect-completion-strategies (list (phpinspect-make-comp-attribute)
(phpinspect-make-comp-sigil)
(phpinspect-make-comp-word)
(phpinspect-make-comp-static-attribute))
"List of completion strategies that phpinspect can use.")
(defun phpinspect--get-completion-query ()
(phpinspect-make-completion-query
:buffer phpinspect-current-buffer
:completion-point (phpinspect--determine-completion-point)
:point (point)))
(cl-defmethod phpinspect-completion-query-execute ((query phpinspect-completion-query))
"Execute QUERY.
Returns list of `phpinspect--completion'."
(let* ((buffer (phpinspect-completion-query-buffer query))
(point (phpinspect-completion-query-point query))
(buffer-map (phpinspect-buffer-parse-map buffer))
(rctx (phpinspect-get-resolvecontext buffer-map point))
(completion-list (phpinspect--make-completion-list)))
(phpinspect-buffer-update-project-index buffer)
(dolist (strategy phpinspect-completion-strategies)
(when-let (region (phpinspect-comp-strategy-supports strategy query rctx))
(setf (phpinspect--completion-list-completion-start completion-list)
(car region)
(phpinspect--completion-list-completion-end completion-list)
(cadr region))
(phpinspect--log "Found matching completion strategy. Executing...")
(dolist (candidate (phpinspect-comp-strategy-execute strategy query rctx))
(phpinspect--completion-list-add
completion-list (phpinspect--make-completion candidate)))))
(setq phpinspect--last-completion-list completion-list)))
(cl-defmethod phpinspect--make-completion
((completion-candidate phpinspect--function))
"Create a `phpinspect--completion` for COMPLETION-CANDIDATE."
(phpinspect--construct-completion
:value (phpinspect--function-name completion-candidate)
:meta (concat "(" (mapconcat (lambda (arg)
(concat "$" (if (> (length (car arg)) 8)
(truncate-string-to-width (car arg) 8 nil)
(car arg))))
(phpinspect--function-arguments completion-candidate)
", ")
") "
(phpinspect--format-type-name (phpinspect--function-return-type completion-candidate)))
:annotation (concat " "
(phpinspect--type-bare-name
(phpinspect--function-return-type completion-candidate)))
:target completion-candidate
:kind 'function))
(cl-defmethod phpinspect--make-completion
((completion-candidate phpinspect--variable))
(phpinspect--construct-completion
:value (phpinspect--variable-name completion-candidate)
:meta (phpinspect--format-type-name
(or (phpinspect--variable-type completion-candidate)
phpinspect--null-type))
:target completion-candidate
:annotation (concat " "
(phpinspect--type-bare-name
(or (phpinspect--variable-type completion-candidate)
phpinspect--null-type)))
:kind 'variable))
(define-inline phpinspect--prefix-for-completion (completion)
(inline-letevals (completion)
(inline-quote
(pcase (phpinspect--completion-kind ,completion)
('function "<f> ")
('variable "<va> ")))))
(defun phpinspect-complete-at-point ()
(catch 'phpinspect-parse-interrupted
(let ((comp-list (phpinspect-completion-query-execute (phpinspect--get-completion-query)))
strings)
(obarray-map (lambda (sym) (push (symbol-name sym) strings)) (phpinspect--completion-list-completions comp-list))
(and (phpinspect--completion-list-has-candidates comp-list)
(list (phpinspect--completion-list-completion-start comp-list)
(phpinspect--completion-list-completion-end comp-list)
strings
:affixation-function
(lambda (completions)
(let (affixated completion)
(dolist (comp completions)
(setq completion (phpinspect--completion-list-get-metadata comp-list comp))
(push (list comp (phpinspect--prefix-for-completion completion)
(phpinspect--completion-meta completion))
affixated))
(nreverse affixated)))
:exit-function
(lambda (comp-name state)
(let ((comp (phpinspect--completion-list-get-metadata
phpinspect--last-completion-list
comp-name)))
(when (and (eq 'finished state)
(eq 'function (phpinspect--completion-kind comp)))
(insert "(")
(when (= 0 (length (phpinspect--function-arguments
(phpinspect--completion-target comp))))
(insert ")")))))
:company-kind (lambda (comp-name)
(let ((comp
(phpinspect--completion-list-get-metadata
phpinspect--last-completion-list
comp-name)))
(if comp
(phpinspect--completion-kind comp)
(phpinspect--log "Unable to find matching completion for name %s" comp-name)
nil))))))))
(provide 'phpinspect-completion)

@ -1,235 +0,0 @@
;;; phpinspect-edtrack.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-util)
(eval-when-compile
(require 'phpinspect-meta)
(phpinspect--declare-log-group 'edtrack))
(cl-defstruct (phpinspect-edtrack (:constructor phpinspect-make-edtrack))
(edits nil
:type list)
(taint-pool nil
:type list)
(last-edit nil
:type cons
:documentation "Last registered edit")
(last-edit-start -1
:type integer
:documentation "Last registered edit start position"))
(defsubst phpinspect-edit-original-end (edit)
(or (caar edit) 0))
(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-edit-end (edit)
(let ((end (or (caar edit) 0))
(previous-edit (cdr edit)))
(+ end (phpinspect-edit-delta previous-edit))))
(defsubst phpinspect-edtrack-original-position-at-point (track point)
(let ((edit (phpinspect-edtrack-edits track))
(encroached)
(pos))
(while (and edit (<= point (phpinspect-edit-end edit)))
(setq edit (cdr edit)))
(setq pos (- point (phpinspect-edit-delta edit)))
;; When point is within the edit delta's range, correct the delta by the
;; amount of encroachment.
(if (< 0 (setq encroached (- (+ (phpinspect-edit-end edit) (or (cdar edit) 0)) point)))
(+ pos encroached)
pos)))
(defsubst phpinspect-edtrack-current-position-at-point (track point)
(let ((edit (phpinspect-edtrack-edits track))
(encroached)
(pos))
(while (and edit (<= point (phpinspect-edit-original-end edit)))
(setq edit (cdr edit)))
(setq pos (+ point (phpinspect-edit-delta edit)))
(if (< 0 (setq encroached (- (+ (phpinspect-edit-original-end edit) (or (cdar edit) 0)) point)))
(- pos encroached)
pos)))
(define-inline phpinspect-taint-start (taint)
(inline-quote (car ,taint)))
(define-inline phpinspect-taint-end (taint)
(inline-quote (cdr ,taint)))
(define-inline phpinspect-make-taint (start end)
(inline-quote (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-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))))))
(defsubst phpinspect-edtrack-register-edit (track start end pre-change-length)
(phpinspect--log
"Edtrack registered change: [start: %d, end: %d, pre-change-length: %d]"
start end pre-change-length)
(let ((original-start (phpinspect-edtrack-original-position-at-point track start)))
(phpinspect-edtrack-register-taint
track original-start (+ original-start 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)))
(let ((delta ;; The delta of this edit.
(- (- end start) pre-change-length))
new-edit)
(setq 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.
(phpinspect-edtrack-original-position-at-point
track (+ start pre-change-length))
delta))
(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-edtrack-clear-taint-pool (track)
(setf (phpinspect-edtrack-taint-pool track) nil))
(defsubst phpinspect-edtrack-clear (track)
(setf (phpinspect-edtrack-edits track) nil)
(setf (phpinspect-edtrack-last-edit track) nil)
(setf (phpinspect-edtrack-last-edit-start track) -1)
(phpinspect-edtrack-clear-taint-pool track))
(defsubst phpinspect-edtrack-make-taint-iterator (track)
(cons (car (phpinspect-edtrack-taint-pool track))
(cl-copy-list (cdr (phpinspect-edtrack-taint-pool track)))))
(define-inline phpinspect-taint-iterator-current (iter)
(inline-quote (car ,iter)))
(define-inline phpinspect-taint-iterator-follow (iter pos)
(inline-letevals (iter pos)
(inline-quote
(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)))))
(define-inline phpinspect-taint-iterator-token-is-tainted-p (iter meta)
(inline-letevals (iter meta)
(inline-quote
(and (phpinspect-taint-iterator-follow ,iter (phpinspect-meta-start ,meta))
(phpinspect-taint-overlaps-meta
(phpinspect-taint-iterator-current ,iter) ,meta)))))
(define-inline phpinspect-taint-iterator-region-is-tainted-p (iter start end)
(inline-letevals (iter start end)
(inline-quote
(and (phpinspect-taint-iterator-follow ,iter ,start)
(phpinspect-taint-overlaps-region
(phpinspect-taint-iterator-current ,iter) ,start ,end)))))
(provide 'phpinspect-edtrack)
;;; phpinspect-edtrack.el ends here

@ -1,283 +0,0 @@
;;; phpinspect-eldoc.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-util)
(require 'phpinspect-meta)
(require 'phpinspect-token-predicates)
(require 'phpinspect-resolve)
(require 'phpinspect-buffer)
(eval-when-compile
(phpinspect--declare-log-group 'eldoc))
(defvar phpinspect-eldoc-word-width 14
"The maximum width of words in eldoc strings.")
(cl-defstruct (phpinspect-eldoc-query (:constructor phpinspect-make-eldoc-query))
(point 0
:type integer
:documentation "Position in buffer for which to provide hints")
(buffer nil
:type phpinspect-buffer))
(cl-defgeneric phpinspect-eld-strategy-supports (strategy query context)
"Should return non-nil if STRATEGY should be deployed for QUERY
and CONTEXT. All strategies must implement this method.")
(cl-defgeneric phpinspect-eld-strategy-execute (strategy query context)
"Should return an object for which `phpinspect-eldoc-string' is implemented.")
(cl-defgeneric phpinspect-eldoc-string (response)
"Should return a string to be displayed by eldoc. This needs to
be implemented for return values of `phpinspect-eld-strategy-execute'")
(cl-defstruct (phpinspect-eld-attribute (:constructor phpinspect-make-eld-attribute))
"Eldoc strategy for object attributes.")
(cl-defmethod phpinspect-eld-strategy-supports
((_strat phpinspect-eld-attribute) (_q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
(phpinspect-attrib-p (car (last (phpinspect--resolvecontext-subject rctx)))))
(cl-defmethod phpinspect-eld-strategy-execute
((_strat phpinspect-eld-attribute) (_q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
(let ((attrib (car (last (phpinspect--resolvecontext-subject rctx))))
type-before)
(setf (phpinspect--resolvecontext-subject rctx) (butlast (phpinspect--resolvecontext-subject rctx)))
(setq type-before (phpinspect-resolve-type-from-context rctx))
(when type-before
(let ((class (phpinspect-project-get-class-extra-or-create
(phpinspect--resolvecontext-project rctx)
type-before))
(attribute-name (cadadr attrib))
variable method result)
(when attribute-name
(cond ((phpinspect-static-attrib-p attrib)
(setq variable (phpinspect--class-get-variable class attribute-name))
(if (and variable
(or (phpinspect--variable-static-p variable)
(phpinspect--variable-const-p variable)))
(setq result variable)
(setq method (phpinspect--class-get-static-method
class (phpinspect-intern-name attribute-name)))
(when method
(setq result (phpinspect-make-function-doc :fn method)))))
((phpinspect-object-attrib-p attrib)
(setq variable (phpinspect--class-get-variable class attribute-name))
(if (and variable
(phpinspect--variable-vanilla-p variable))
(setq result variable)
(setq method (phpinspect--class-get-method
class (phpinspect-intern-name attribute-name)))
(when method
(setq result (phpinspect-make-function-doc :fn method))))))
result)))))
(cl-defstruct (phpinspect-eld-function-args (:constructor phpinspect-make-eld-function-args))
"Eldoc strategy for function arguments.")
(cl-defmethod phpinspect-eld-strategy-supports
((_strat phpinspect-eld-function-args) (_q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
(let ((parent-token (car (phpinspect--resolvecontext-enclosing-tokens rctx))))
;; When our subject is inside a list, it is probably an argument of a
;; function/method call, which is what this strategy provides information for.
(or (phpinspect-list-p parent-token)
;; When the last token in our subject is a list, we're either at the end
;; of a buffer in an incomplete argument list (no closing paren), or in
;; an empty argument list of a function call.
(phpinspect-list-p
(car (last (phpinspect--resolvecontext-subject rctx)))))))
(cl-defmethod phpinspect-eld-strategy-execute
((_strat phpinspect-eld-function-args) (q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
(phpinspect--log "Executing `phpinspect-eld-function-args' strategy")
(let* ((enclosing-token (car (phpinspect--resolvecontext-enclosing-metadata
rctx)))
(left-sibling )
(statement )
match-result static arg-list arg-pos)
(cond
;; Subject is a statement
((and (phpinspect-list-p (car (last (phpinspect--resolvecontext-subject rctx))))
enclosing-token)
(setq left-sibling (phpinspect-meta-find-child-before-recursively
enclosing-token (phpinspect-eldoc-query-point q))))
;; Subject is inside an argument list
((and enclosing-token
(phpinspect-list-p (phpinspect-meta-token enclosing-token)))
(setq left-sibling (phpinspect-meta-find-left-sibling enclosing-token)
statement (list enclosing-token))))
(phpinspect--log "Left sibling: %s" (phpinspect-meta-string left-sibling))
(phpinspect--log "Enclosing parent: %s" (phpinspect-meta-string (phpinspect-meta-parent enclosing-token)))
(while (and left-sibling
(not (phpinspect-statement-introduction-p (phpinspect-meta-token left-sibling))))
(unless (phpinspect-comment-p (phpinspect-meta-token left-sibling))
(push left-sibling statement))
(setq left-sibling (phpinspect-meta-find-left-sibling left-sibling)))
(phpinspect--log "Eldoc statement is: %s" (mapcar #'phpinspect-meta-token statement))
(phpinspect--log "Enclosing token was: %s" (phpinspect-meta-token enclosing-token))
(when enclosing-token
(cond
;; Method call
((setq match-result (phpinspect--match-sequence (last statement 2)
:f (phpinspect-meta-wrap-token-pred #'phpinspect-attrib-p)
:f (phpinspect-meta-wrap-token-pred #'phpinspect-list-p)))
(phpinspect--log "Eldoc context is a method call")
(setq arg-list (car (last match-result))
static (phpinspect-static-attrib-p (phpinspect-meta-token (car match-result)))
arg-pos (seq-reduce
(lambda (count meta)
(if (phpinspect-comma-p (phpinspect-meta-token meta))
(+ count 1)
count))
(phpinspect-meta-find-children-before arg-list (phpinspect-eldoc-query-point q)) 0))
;; Set resolvecontext subject to the statement minus the method
;; name. Point is likely to be at a location inside a method call like
;; "$a->b->doSomething(". The resulting subject should be "$a->b".
(setf (phpinspect--resolvecontext-subject rctx)
(mapcar #'phpinspect-meta-token (butlast statement 2)))
(when-let* ((type-of-previous-statement
(phpinspect-resolve-type-from-context rctx))
(method-name-sym (phpinspect-intern-name (cadadr (phpinspect-meta-token (car match-result)))))
(class (phpinspect-project-get-class-extra-or-create
(phpinspect--resolvecontext-project rctx)
type-of-previous-statement))
(method (if static
(phpinspect--class-get-static-method class method-name-sym)
(phpinspect--class-get-method class method-name-sym))))
(when method
(phpinspect-make-function-doc :fn method :arg-pos arg-pos))))
((setq match-result (phpinspect--match-sequence (last statement 2)
:f (phpinspect-meta-wrap-token-pred #'phpinspect-word-p)
:f (phpinspect-meta-wrap-token-pred #'phpinspect-list-p)))
(phpinspect--log "Eldoc context is a function call")
(setq arg-list (car (last match-result))
arg-pos (seq-reduce
(lambda (count meta)
(if (phpinspect-comma-p (phpinspect-meta-token meta))
(+ count 1)
count))
(phpinspect-meta-find-children-before arg-list (phpinspect-eldoc-query-point q)) 0))
(let ((func (phpinspect-project-get-function-or-extra
(phpinspect--resolvecontext-project rctx)
(phpinspect-intern-name (cadr (phpinspect-meta-token (car match-result)))))))
(phpinspect--log "Got past that")
(when func
(phpinspect-make-function-doc :fn func :arg-pos arg-pos))))))))
(cl-defmethod phpinspect-eldoc-string ((var phpinspect--variable))
(concat (truncate-string-to-width
(propertize (concat (if (phpinspect--variable-vanilla-p var) "$" "")
(phpinspect--variable-name var))
'face 'font-lock-variable-name-face)
phpinspect-eldoc-word-width)
": "
(propertize (phpinspect--format-type-name (phpinspect--variable-type var))
'face 'font-lock-type-face)))
(cl-defstruct (phpinspect-function-doc (:constructor phpinspect-make-function-doc))
(fn nil
:type phpinspect--function)
(arg-pos nil))
(cl-defmethod phpinspect-eldoc-string ((doc phpinspect-function-doc))
(let ((fn (phpinspect-function-doc-fn doc))
(arg-pos (phpinspect-function-doc-arg-pos doc))
(arg-count 0))
(concat (truncate-string-to-width
(phpinspect--function-name fn) phpinspect-eldoc-word-width) ": ("
(mapconcat
(lambda (arg)
(let ((doc-string
(concat "$" (truncate-string-to-width
(car arg) phpinspect-eldoc-word-width)
(if (cadr arg) " " "")
(phpinspect--format-type-name (or (cadr arg) "")))))
(when (and arg-pos (= arg-count arg-pos))
(setq doc-string
(propertize
doc-string 'face 'eldoc-highlight-function-argument)))
(setq arg-count (+ arg-count 1))
doc-string))
(phpinspect--function-arguments fn)
", ")
"): "
(propertize
(phpinspect--format-type-name (phpinspect--function-return-type fn))
'face 'font-lock-type-face))))
(defvar phpinspect-eldoc-strategies (list (phpinspect-make-eld-attribute)
(phpinspect-make-eld-function-args))
"The eldoc strategies that phpinspect is currently allowed to
employ. Strategies are queried in the order of this list. See
also `phpinspect-eldoc-query-execute'.")
(cl-defmethod phpinspect-eldoc-query-execute ((query phpinspect-eldoc-query))
(let* ((buffer (phpinspect-eldoc-query-buffer query))
(point (phpinspect-eldoc-query-point query))
(buffer-map (phpinspect-buffer-parse-map buffer))
(rctx (phpinspect-get-resolvecontext buffer-map point)))
(phpinspect-buffer-update-project-index buffer)
(catch 'matched
(dolist (strategy phpinspect-eldoc-strategies)
(when (phpinspect-eld-strategy-supports strategy query rctx)
(phpinspect--log "Found matching eldoc strategy. Executing...")
(throw 'matched (phpinspect-eld-strategy-execute strategy query rctx)))))))
(defun phpinspect-eldoc-function ()
"An `eldoc-documentation-function` implementation for PHP files.
Ignores `eldoc-argument-case` and `eldoc-echo-area-use-multiline-p`.
TODO:
- Respect `eldoc-echo-area-use-multiline-p`
"
(catch 'phpinspect-parse-interrupted
(let ((resp (phpinspect-eldoc-query-execute
(phpinspect-make-eldoc-query
:buffer phpinspect-current-buffer
:point (phpinspect--determine-completion-point)))))
(when resp
(phpinspect-eldoc-string resp)))))
(provide 'phpinspect-eldoc)

@ -1,176 +0,0 @@
;;; phpinspect-fs.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
(defconst phpinspect--cat-executable (executable-find "cat")
"The executable used to read files asynchronously from the filesystem.")
(cl-defstruct (phpinspect-fs (:constructor phpinspect-make-fs)))
(cl-defstruct (phpinspect-virtual-fs (:constructor phpinspect-make-virtual-fs))
"A rough in-memory filesystem. Useful for testing."
(files (make-hash-table :test 'equal)
:type hash-table
:documentation
"The files in the virtual filesystem"))
(defsubst phpinspect-make-virtual-file (contents)
(list contents (current-time)))
(defalias 'phpinspect-virtual-file-modification-time #'cadr)
(defalias 'phpinspect-virtual-file-contents #'car)
(defun phpinspect-virtual-fs-set-file (fs path contents)
(declare (indent defun))
(puthash path (phpinspect-make-virtual-file contents)
(phpinspect-virtual-fs-files fs)))
(cl-defgeneric phpinspect-fs-file-exists-p (fs file))
(cl-defgeneric phpinspect-fs-file-directory-p (fs file))
(cl-defgeneric phpinspect-fs-file-modification-time (fs file))
(cl-defgeneric phpinspect-fs-insert-file-contents (fs file &optional prefer-async)
"Insert file contents of FILE.
When PREFER-ASYNC is set and FS supports it, effort is made to
execute the insertion asynchronously in scenario's where this can
prevent the main thread (or other running threads) from stalling
while the current thread executes. When running in the main
thread, PREFER-ASYNC has no effect.")
(cl-defgeneric phpinspect-fs-directory-files (fs directory match))
(cl-defgeneric phpinspect-fs-directory-files-recursively (fs directory match))
(cl-defmethod phpinspect-fs-file-exists-p ((_fs phpinspect-fs) file)
(file-exists-p file))
(cl-defmethod phpinspect-fs-file-exists-p ((fs phpinspect-virtual-fs) file)
(and (gethash file (phpinspect-virtual-fs-files fs)) t))
(cl-defmethod phpinspect-fs-file-directory-p ((_fs phpinspect-fs) file)
(file-directory-p file))
(cl-defmethod phpinspect-fs-file-directory-p ((fs phpinspect-virtual-fs) file)
(setq file (concat (string-remove-suffix "/" file) "/"))
(let ((is-directory? nil))
(maphash
(lambda (existing-file _ignored)
(when (string-prefix-p (file-name-directory existing-file)
file)
(setq is-directory? t)))
(phpinspect-virtual-fs-files fs))
is-directory?))
(cl-defmethod phpinspect-fs-file-modification-time ((fs phpinspect-virtual-fs) file)
(let ((file (gethash file (phpinspect-virtual-fs-files fs))))
(when file
(phpinspect-virtual-file-modification-time file))))
(cl-defmethod phpinspect-fs-file-modification-time ((_fs phpinspect-fs) file)
(let ((attributes (file-attributes file)))
(when attributes
(file-attribute-modification-time attributes))))
(defsubst phpinspect--insert-file-contents-asynchronously (file)
"Inserts FILE contents into the current buffer asynchronously,
while blocking the current thread.
Errors when executed in main thread, as it should be used to make
background operations less invasive. Usage in the main thread can
only be the result of a logic error."
(let* ((thread (current-thread))
(mx (make-mutex))
(condition (make-condition-variable mx))
(err)
(sentinel
(lambda (process event)
(with-mutex mx
(if (string-match-p "^\\(deleted\\|exited\\|failed\\|connection\\)" event)
(progn
(setq err (format "cat process %s failed with event: %s" process event))
(condition-notify condition))
(when (string-match-p "^finished" event)
(condition-notify condition)))))))
(when (not phpinspect--cat-executable)
(error
"ERROR: phpinspect--insert-file-contents-asynchronously called when cat-executable is not set"))
(when (eq thread main-thread)
(error "ERROR: phpinspect--insert-file-contents-asynchronously called from main-thread"))
(with-mutex mx
(make-process :name "phpinspect--insert-file-contents-asynchronously"
:command `(,phpinspect--cat-executable ,file)
:buffer (current-buffer)
:sentinel sentinel)
(condition-wait condition)
(when err (error err)))))
(cl-defmethod phpinspect-fs-insert-file-contents ((_fs phpinspect-fs) file &optional prefer-async)
"Insert file contents from FILE. "
(if (and prefer-async (not (eq (current-thread) main-thread))
phpinspect--cat-executable)
(phpinspect--insert-file-contents-asynchronously file)
(insert-file-contents-literally file)))
(cl-defmethod phpinspect-fs-insert-file-contents ((fs phpinspect-virtual-fs) file &optional _ignored)
(let ((file-obj (gethash file (phpinspect-virtual-fs-files fs))))
(when file (insert (or (phpinspect-virtual-file-contents file-obj) "")))))
(cl-defmethod phpinspect-fs-directory-files ((_fs phpinspect-fs) directory &optional match)
(directory-files directory t match t))
(cl-defmethod phpinspect-fs-directory-files ((fs phpinspect-virtual-fs) directory &optional match)
(setq directory (replace-regexp-in-string "[/]+" "/" (concat directory "/")))
(let ((files))
(maphash
(lambda (file _ignored)
(let ((basename (replace-regexp-in-string "/.*$" "" (string-remove-prefix directory file))))
(when (and (string-prefix-p directory file)
(string-match-p "^[^/]*$" basename)
(if match (string-match-p match basename) t))
(cl-pushnew (concat directory basename) files :test #'string=))))
(phpinspect-virtual-fs-files fs))
files))
(cl-defmethod phpinspect-fs-directory-files-recursively ((_fs phpinspect-fs) directory &optional match)
(directory-files-recursively directory
match
t ;; Ignore directories that cannot be read
t ;; follow symlinks
))
(cl-defmethod phpinspect-fs-directory-files-recursively ((fs phpinspect-virtual-fs) directory &optional match)
(setq directory (replace-regexp-in-string "[/]+" "/" (concat directory "/")))
(let ((files))
(maphash
(lambda (file _ignored)
(when (and (string-prefix-p directory file)
(if match (string-match-p match file) t))
(push file files)))
(phpinspect-virtual-fs-files fs))
files))
(provide 'phpinspect-fs)
;;; phpinspect-fs.el ends here

@ -1,165 +0,0 @@
; phpinspect-imports.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
;; See docstrings for documentation, starting with `phpinspect-mode'.
;;; Code:
(require 'phpinspect-token-predicates)
(require 'phpinspect-index)
(require 'phpinspect-autoload)
(require 'phpinspect-buffer)
(require 'phpinspect-cache)
(require 'phpinspect-util)
(defun phpinspect-insert-at-point (point data)
(save-excursion
(goto-char point)
(insert data)))
(defun phpinspect-find-first-use (token-meta)
(if (and (phpinspect-namespace-p (phpinspect-meta-token token-meta))
(phpinspect-namespace-is-blocked-p (phpinspect-meta-token token-meta)))
(phpinspect-find-first-use (phpinspect-meta-last-child token-meta))
(phpinspect-meta-find-first-child-matching
token-meta (phpinspect-meta-wrap-token-pred #'phpinspect-use-p))))
(defun phpinspect-add-use (fqn buffer &optional namespace-meta)
"Add use statement for FQN to BUFFER.
If NAMESPACE-TOKEN is non-nil, it is assumed to be a token that
was parsed from BUFFER and its location will be used to find a
buffer position to insert the use statement at."
(when (string-match "^\\\\" fqn)
(setq fqn (string-trim-left fqn "\\\\")))
(if namespace-meta
(let* ((namespace-block (and (phpinspect-namespace-is-blocked-p
(phpinspect-meta-token namespace-meta))
(phpinspect-meta-last-child namespace-meta)))
(existing-use (phpinspect-find-first-use namespace-meta)))
(if existing-use
(phpinspect-insert-at-point
(phpinspect-meta-start existing-use) (format "use %s;%c" fqn ?\n))
(if namespace-block
(phpinspect-insert-at-point
(+ 1 (phpinspect-meta-start namespace-block))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-insert-at-point
(phpinspect-meta-end
(phpinspect-meta-find-first-child-matching
namespace-meta (phpinspect-meta-wrap-token-pred #'phpinspect-terminator-p)))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n)))))
;; else
(let ((existing-use (phpinspect-meta-find-first-child-matching
(phpinspect-buffer-root-meta buffer)
(phpinspect-meta-wrap-token-pred #'phpinspect-use-p))))
(if existing-use
(phpinspect-insert-at-point
(phpinspect-meta-start existing-use)
(format "use %s;%c" fqn ?\n))
(let* ((first-token (phpinspect-meta-first-child (phpinspect-buffer-root-meta buffer)))
token-after)
(when (and (phpinspect-word-p (phpinspect-meta-token first-token))
(string= "declare" (cadr (phpinspect-meta-token first-token))))
(progn
(setq token-after first-token)
(while (and token-after (not (phpinspect-terminator-p
(phpinspect-meta-token token-after))))
(setq token-after (phpinspect-meta-find-right-sibling token-after)))))
(if token-after
(phpinspect-insert-at-point
(phpinspect-meta-end token-after) (format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-insert-at-point
(phpinspect-meta-start first-token)
(format "%c%cuse %s;%c%c" ?\n ?\n fqn ?\n ?\n))))))))
(defun phpinspect-add-use-interactive (typename buffer project &optional namespace-token)
(let* ((autoloader (phpinspect-project-autoload project))
(fqns (phpinspect-autoloader-get-type-bag autoloader typename)))
(cond ((= 1 (length fqns))
(phpinspect-add-use (phpinspect-name-string (car fqns)) buffer namespace-token))
((> (length fqns) 1)
(phpinspect-add-use (completing-read "Class: " (phpinspect-names-to-alist fqns))
buffer namespace-token))
(t (phpinspect-message "No import found for type %s" typename)))))
(defun phpinspect-namespace-part-of-typename (typename)
(string-trim-right typename "\\\\?[^\\]+"))
(defalias 'phpinspect-fix-uses-interactive #'phpinspect-fix-imports
"Alias for backwards compatibility")
(defun phpinspect-add-use-statements-for-missing-types (types buffer imports project parent-token)
(let (namespace namespace-name)
(dolist (type types)
(setq namespace (phpinspect-meta-find-parent-matching-token
parent-token #'phpinspect-namespace-p)
namespace-name (phpinspect-namespace-name namespace))
;; Add use statements for types that aren't imported or already referenced
;; with a fully qualified name.
(unless (or (or (alist-get type imports))
(gethash (phpinspect-intern-name
(concat namespace-name "\\" (phpinspect-name-string type)))
(phpinspect-autoloader-types
(phpinspect-project-autoload project))))
(phpinspect-add-use-interactive type buffer project namespace)
(phpinspect-buffer-parse buffer 'no-interrupt)))))
(defun phpinspect-fix-imports ()
"Find types that are used in the current buffer and make sure
that there are import (\"use\") statements for them."
(interactive)
(if phpinspect-current-buffer
(let* ((buffer phpinspect-current-buffer)
(tree (phpinspect-buffer-parse buffer))
(index (phpinspect--index-tokens
tree nil (phpinspect-buffer-location-resolver buffer)))
(classes (alist-get 'classes index))
(imports (alist-get 'imports index))
(project (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect-current-project-root)))
(used-types (alist-get 'used-types index)))
(phpinspect-add-use-statements-for-missing-types
used-types buffer imports project (phpinspect-buffer-root-meta buffer))
(dolist (class classes)
(let* ((class-imports (alist-get 'imports class))
(used-types (alist-get 'used-types class))
(class-name (alist-get 'class-name class))
(region (alist-get 'location class))
token-meta)
(setq token-meta (phpinspect-meta-find-parent-matching-token
(phpinspect-bmap-last-token-before-point
(phpinspect-buffer-map buffer)
(+ (phpinspect-region-start region) 1))
#'phpinspect-class-p))
(unless token-meta
(error "Unable to find token for class %s" class-name))
(phpinspect-add-use-statements-for-missing-types
used-types buffer (append imports class-imports) project token-meta))))))
(provide 'phpinspect-imports)

@ -0,0 +1,853 @@
#!/bin/bash
##
# phpinspect-index.bash - Resolve namespaces and fix missing use statements in your PHP
# scripts.
###
# This script is derived from phpns, an earlier project that had a much wider scope than
# just index files for phpinspect.el. Much of the code and command line argument options
# can be removed.
# TODO: remove whatever functionality is not required for phpinspect.el
# shellcheck disable=SC2155
declare CACHE_DIR=./.cache/phpinspect
declare INFO=1
# Cache locations
declare CLASSES="$CACHE_DIR/classes"
declare NAMESPACES="$CACHE_DIR/namespaces"
declare USES="$CACHE_DIR/uses"
declare USES_OWN="$CACHE_DIR/uses_own"
declare USES_LOOKUP="$CACHE_DIR/uses_lookup"
declare USES_LOOKUP_OWN="$CACHE_DIR/uses_lookup_own"
declare FILE_PATHS="$CACHE_DIR/file_paths"
declare NAMESPACE_FILE_PATHS="$CACHE_DIR/namespace_file_paths"
declare INDEXED="$CACHE_DIR/indexed"
[[ $DEBUG -eq 2 ]] && set -x
shopt -s extglob
shopt -so pipefail
read -rd '' USAGE <<'EOF'
phpns - Resolve namespaces and fix missing use statements in your PHP scripts.
USAGE:
phpns COMMAND [ ARGUMENTS ] [ OPTIONS ]
COMMANDS:
i, index Index the PHP project in the current directory
fu, find-use CLASS_NAME Echo the FQN of a class
fxu, fix-uses FILE Add needed use statements to FILE
cns, classes-in-namespace NAMESPACE Echo the classes that reside in NAMESPACE
fp, filepath FQN Echo the filepath of the class by the name of FQN.
TO BE IMPLEMENTED:
rmuu, remove-unneeded-uses FILE: Remove all use statements for classes that are not being used.
OPTIONS FOR ALL COMMANDS:
-s --silent Don't print info.
UNIQUE OPTIONS PER COMMAND:
index:
-d, --diff Show differences between the files in the index and the files in the project directory.
-N, --new Only index new files
find-use:
-j, --json Provide possible use FQN's as a json array.
-p, --prefer-own If there are matches inside the "src" dir, only use those.
-a, --auto-pick Use first encountered match, don't provide a choice.
-b. --bare Print FQN's without any additives.
fix-uses:
-j, --json Provide possible use FQN's per class as a json object with the class names as keys.
-p, --prefer-own If there are matches inside the "src" dir, only use those.
-a, --auto-pick Use first encountered match, for every class, don't provide a choice.
-o, --stdout Print to stdout in stead of printing to the selected file.
filepath: -
EOF
execute() {
declare command="$1" INFO="$INFO"
declare -a CONFIG=()
shift
if [[ $command == @(-h|--help|help) ]]; then
echo "$USAGE" >&2
exit 0
fi
if ! [[ -f ./composer.json ]] && ! [[ -d ./.git ]]; then
echo "No composer.json or .git file found, not in root of poject, exiting." >&2
exit 1
fi
case "$command" in
i | index)
handleArguments index "$@" || return $?
# The arguments to grep need to be dynamic here, because the diff option
# requires different arguments to be passed to grep.
declare -a grep_args=(
-H
'^\(class\|abstract[[:blank:]]\+class\|\(final[[:blank:]]\+\|/\*[[:blank:]]*final[[:blank:]]*\*/[[:blank:]]*\)class\|namespace\|interface\|trait\)[[:blank:]]\+[A-Za-z]\+'
--exclude-dir={.cache,var,bin}
--binary-files=without-match
)
# Only index new files
if [[ ${CONFIG[$INDEX_NEW]} == '--new' ]]; then
declare -a new_files=() deleted_files=()
# Extract new files from diff.
while IFS=':' read -ra diff_file; do
if [[ ${diff_file[0]} == '-' ]]; then
deleted_files=("${diff_file[1]}" "${deleted_files[@]}")
elif [[ ${diff_file[0]} == '+' ]]; then
new_files=("${diff_file[1]}" "${new_files[@]}")
fi
done < <(diffIndex)
# Inform the user if non-existent files were found. Right now the only
# way to fix this is to reindex entirely.
if [[ ${#deleted_files[@]} -gt 0 ]]; then
info "There are ${#deleted_files[@]} non-existent files in your index. Consider reindexing to prevent incorrect results."
info 'Some of these none existent files are:'
for i in {0..19}; do
[[ $i -ge ${#deleted_files[@]} ]] && break
infof ' - "%s"\n' "${deleted_files[$i]}"
done
fi
if [[ ${#new_files[@]} -eq 0 ]]; then
info 'No new files were found.'
return 0
else
info "${#new_files[@]} new files found to index."
fi
# To exclusively index new files, add the filenames to the arguments array
grep_args=("${grep_args[@]}" "${new_files[@]}")
elif [[ ${CONFIG[$INDEX_DIFF]} == '--diff' ]]; then
diffIndex
return $?
else
grep_args=("${grep_args[@]}" '-r' '--include=*.php')
fi
# Index matching files
grep -m 2 "${grep_args[@]}" | grep -v '^vendor/bin' | fillIndex
# Add non-matching files to the file with indexed files.
# This is necessary to be able to diff the index.
grep -L "${grep_args[@]}" | grep -v '^vendor/bin' >> "$INDEXED"
;;
fu | find-use)
checkCache
handleArguments find-use "$@" || return $?
declare use_path='' class_name="${CONFIG[$CLASS_NAME]}"
if [[ "$class_name" == @(array|string|float|int|void|mixed) ]]; then
infof 'Type "%s" is not a class, but a primitive type.\n' "$class_name"
return 1
fi
findUsePathForClass "$class_name"
;;
fxu | fix-uses)
checkCache
handleArguments fix-uses "$@" || return $?
declare file="${CONFIG[$FILE]}"
if ! [[ -f $file ]]; then
infof 'File "%s" does not exist or is not a regular file.\n' "$file"
return 1
elif [[ ${CONFIG[$STDOUT]} == '--stdout' ]]; then
fixMissingUseStatements "$file"
else
# shellcheck disable=SC2005
echo "$(fixMissingUseStatements "$file")" > "$file"
fi
;;
ns | namespace)
checkCache
declare file="$1"
# Try the index, if that doesn't work, attempt to extract the namespace from the file itself.
if ! grep "(?<=$file:).*" "$NAMESPACE_FILE_PATHS"; then
grep -Po '(?<=^namespace[[:blank:]])[A-Za-z_\\]+' "$file"
fi
;;
cns | classes-in-namespace)
handleArguments classes-in-namespace "$@" || return $?
checkCache
declare namespace="${CONFIG[$NAMESPACE]}\\"
debug "Checking for namespace $namespace"
awk -F ':' "/:${namespace//\\/\\\\}"'[^\\]+$/{ print $1; }' "$USES_LOOKUP"
;;
fp | filepath)
handleArguments filepath "$@" || return $?
checkCache
grep -Po "^.*(?=:${CONFIG[$CLASS_PATH]//\\/\\\\}$)" "$FILE_PATHS"
;;
*)
printf 'Command "%s" is not a valid subcommand.\n' "$command" >&2
exit 1
;;
esac
}
# shellcheck disable=SC2034
fixMissingUseStatements() {
declare check_uses='false' check_needs='false' file="$1" namespace="$2"
declare -A uses=() needs=() namespace=()
declare -a classes=()
classes=($(execute cns "$(execute ns "$file")"))
for class in "${classes[@]}"; do
namespace["$class"]='in_namespace'
done
findUsesAndNeeds < "$file"
addUseStatements "${!needs[@]}" < "$file"
}
findUsePathForClass() {
declare class="$1"
if [[ ${CONFIG[$PREFER_OWN]} == '--prefer-own' ]]; then
declare -a possibilities=($(grep -Po "(?<=^${CONFIG[$CLASS_NAME]}:).*" "$USES_LOOKUP_OWN"))
else
declare -a possibilities=($(grep -Po "(?<=^${CONFIG[$CLASS_NAME]}:).*" "$USES_LOOKUP"))
fi
if [[ ${#possibilities[@]} -eq 1 ]]; then
use_path="${possibilities[0]}"
debugf 'Single use path "%s" found' "${possibilities[0]}"
# Provide an escaped string for json output if requested.
[[ ${CONFIG[$JSON]} == '--json' ]] && printf -v use_path '"%s"' "${use_path//\\/\\\\}"
elif [[ ${#possibilities[@]} -eq 0 ]]; then
_handle_no_use
return $?
else
_handle_multiple_uses
fi
infof 'Found use statement for "%s"\n' "$use_path" >&2
if [[ ${CONFIG[$JSON]} == '--json' ]]; then
echo '['
echo "$use_path"
printf ']'
elif [[ ${CONFIG[$BARE]} ]]; then
echo "$use_path"
else
echo "use $use_path;"
fi
}
_handle_no_use() {
declare tried_index_new="$1"
if [[ $tried_index_new != true ]]; then
execute index --silent --new
execute fu "${CONFIG[@]}"
return $?
elif [[ ${CONFIG[$PREFER_OWN]} == '--prefer-own' ]]; then
CONFIG[$PREFER_OWN]=
execute fu "${CONFIG[@]}"
return $?
else
infof 'No match found for class "%s"\n' "$class_name" >&2
[[ ${CONFIG[$JSON]} == '--json' ]] && printf '[]'
fi
return 1
}
_handle_multiple_uses() {
if [[ ${CONFIG[$AUTO_PICK]} == '--auto-pick' ]]; then
use_path="${possibilities[0]}"
return 0
elif [[ ${CONFIG[$BARE]} == '--bare' ]]; then
use_path="$(printf '%s\n' "${possibilities[@]}")"
return 0
elif [[ ${CONFIG[$JSON]} == '--json' ]]; then
use_path="$(
for i in "${!possibilities[@]}"; do
printf '"%s"' "${possibilities[$i]//\\/\\\\}"
[[ $i -lt $((${#possibilities[@]}-1)) ]] && printf ','
echo
done
)"
return 0
fi
infof 'Multiple matches for class "%s", please pick one.\n' "$class_name" >&2
select match in "${possibilities[@]}"; do
use_path="$match"
break
done < /dev/tty
}
addUseStatements() {
declare -a needs=("$@")
declare use_statements=''
if [[ ${CONFIG[$JSON]} == '--json' ]]; then
declare -i length="$((${#needs[@]}-1))" current=0
echo '{'
for needed in "${needs[@]}"; do
printf '"%s": ' "$needed"
execute fu --json "$needed" "${CONFIG[$PREFER_OWN]}" "${CONFIG[$AUTO_PICK]}"
[[ $((current++)) -lt $length ]] && printf ','
echo
done
echo '}'
return 0
fi
while IFS='' read -r line; do
echo "$line"
if [[ $line == namespace* ]]; then
IFS='' read -r line && echo "$line"
use_statements="$(
for needed in "${needs[@]}"; do
execute fu "$needed" "${CONFIG[$PREFER_OWN]}" "${CONFIG[$AUTO_PICK]}"
done | sort
)"
[[ -n $use_statements ]] && echo "$use_statements"
fi
done
declare -i added_uses=0
added_uses="$(echo -n "$use_statements" | wc -l)"
[[ -n $use_statements ]] && ((added_uses++))
info "$added_uses use statements added out of ${#needs[@]} needed types. Types that were needed:" >&2
infof ' - "%s"\n' "${needs[@]}" >&2
}
debug() {
if [[ $DEBUG -ge 1 ]]; then
echo "[DEBUG] => $1" >&2
fi
}
# shellcheck disable=SC2059
debugf() {
if [[ $DEBUG -ge 1 ]]; then
declare format_string="$1"
shift
printf "[DEBUG] => $format_string" "$@" >&2
fi
}
info() {
if [[ $INFO -eq 1 ]]; then
echo "[INFO] => $1" >&2
fi
}
# shellcheck disable=SC2059
infof() {
if [[ $INFO -eq 1 ]]; then
declare format_string="$1"
shift
printf "[INFO] => $format_string" "$@" >&2
fi
}
##
# Functions for parameter parsing
# Enum for config
declare -gri CLASS_NAME=0
declare -gri PREFER_OWN=1
declare -gri AUTO_PICK=2
declare -gri STDOUT=3
declare -gri JSON=4
declare -gri BARE=5
declare -gri WORD=6
declare -gri EXPAND_CLASSES=7
declare -gri NO_CLASSES=8
declare -gri NAMESPACE=9
declare -gri CLASS_PATH=10
declare -gri INDEX_DIFF=11
declare -gri NO_VENDOR=12 # Keep this around as it might be used later on
declare -gri INDEX_NEW=13
declare -gri FILE=14
handleArguments() {
declare -p CONFIG &>>/dev/null || return 1
declare command="$1"
shift
case "$command" in
find-use)
_handle_find_use_arguments "$@" || return $?
;;
fix-uses)
_handle_fix_uses_arguments "$@" || return $?
;;
index)
_handle_index_arguments "$@" || return $?
;;
classes-in-namespace)
_handle_classes_in_namespace_arguments "$@" || return $?
;;
filepath)
_handle_filepath_arguments "$@" || return $?
;;
*)
printf 'handleArguments (line %s): Unknown command "%s" passed.\n' "$(caller)" "$command">&2
return 1
;;
esac
}
_handle_filepath_arguments() {
declare arg="$1"
while shift; do
case "$arg" in
-s | --silent)
INFO=0
;;
--*)
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
;;
-*)
if [[ ${#arg} -gt 2 ]]; then
declare -i i=1
while [[ $i -lt ${#arg} ]]; do
_handle_filepath_arguments "-${arg:$i:1}"
((i++))
done
else
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
fi
;;
'')
:
;;
*)
if [[ -n ${CONFIG[$CLASS_PATH]} ]]; then
printf 'Unexpected argument: "%s"\n' "$arg" >&2
return 1
fi
CONFIG[$CLASS_PATH]="$arg"
esac
arg="$1"
done
}
_handle_classes_in_namespace_arguments() {
declare arg="$1"
while shift; do
case "$arg" in
-s | --silent)
INFO=0
;;
--*)
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
;;
-*)
if [[ ${#arg} -gt 2 ]]; then
declare -i i=1
while [[ $i -lt ${#arg} ]]; do
_handle_classes_in_namespace_arguments "-${arg:$i:1}"
((i++))
done
else
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
fi
;;
'')
:
;;
*)
if [[ -n ${CONFIG[$NAMESPACE]} ]]; then
printf 'Unexpected argument: "%s"\n' "$arg" >&2
return 1
fi
CONFIG[$NAMESPACE]="$arg"
esac
arg="$1"
done
}
_handle_index_arguments() {
declare arg="$1"
while shift; do
case "$arg" in
-s | --silent)
INFO=0
;;
-d | --diff)
CONFIG[$INDEX_DIFF]='--diff'
;;
-N | --new)
CONFIG[$INDEX_NEW]='--new'
;;
--*)
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
;;
-*)
if [[ ${#arg} -gt 2 ]]; then
declare -i i=1
while [[ $i -lt ${#arg} ]]; do
_handle_index_arguments "-${arg:$i:1}"
((i++))
done
else
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
fi
;;
*)
printf 'Unexpected argument: "%s"\n' "$arg" >&2
return 1
;;
esac
arg="$1"
done
}
_handle_fix_uses_arguments() {
declare arg="$1"
while shift; do
case "$arg" in
-s | --silent)
INFO=0
;;
-p | --prefer-own)
CONFIG[$PREFER_OWN]='--prefer-own'
;;
-a | --auto-pick)
CONFIG[$AUTO_PICK]='--auto-pick'
;;
-o | --stdout)
CONFIG[$STDOUT]='--stdout'
INFO=0
;;
-j | --json)
CONFIG[$STDOUT]='--stdout'
CONFIG[$JSON]='--json'
;;
--*)
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
;;
-*)
if [[ ${#arg} -gt 2 ]]; then
declare -i i=1
while [[ $i -lt ${#arg} ]]; do
_handle_fix_uses_arguments "-${arg:$i:1}"
((i++))
done
else
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
fi
;;
'')
:
;;
*)
if [[ -n ${CONFIG[$FILE]} ]]; then
printf 'Unexpected argument: "%s"\n' "$arg" >&2
return 1
fi
CONFIG[$FILE]="$arg"
esac
arg="$1"
done
}
# shellcheck disable=SC2034
_handle_find_use_arguments() {
declare arg="$1"
while shift; do
case "$arg" in
-s | --silent)
INFO=0
;;
-b | --bare)
CONFIG[$BARE]='--bare'
;;
-p | --prefer-own)
CONFIG[$PREFER_OWN]='--prefer-own'
;;
-a | --auto-pick)
CONFIG[$AUTO_PICK]='--auto-pick'
;;
-j | --json) CONFIG[$STDOUT]='--stdout'
CONFIG[$JSON]='--json'
INFO=0
;;
--*)
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
;;
-*)
if [[ ${#arg} -gt 2 ]]; then
declare -i i=1
while [[ $i -lt ${#arg} ]]; do
_handle_find_use_arguments "-${arg:$i:1}"
((i++))
done
else
printf 'Unknown option: "%s"\n' "${arg}" >&2
return 1
fi
;;
'')
:
;;
*)
if [[ -n ${CONFIG[$CLASS_NAME]} ]]; then
printf 'Unexpected argument: "%s"\n' "$arg" >&2
return 1
fi
CONFIG[$CLASS_NAME]="$arg"
esac
arg="$1"
done
}
##
# This function outputs the difference between the files that are present in the
# index and the files that are present in the project directory. The output format is:
# +:NEW_FILE (**Not in index but exists on disk**)
# -:DELETED_FILE (**In index but does not exist on disk**)
##
diffIndex() {
diff --unchanged-line-format='' --new-line-format='+:%L' --old-line-format='-:%L' \
<(sort -u < "$INDEXED" | sed '/^[[:blank:]]*$/d') \
<(find ./ -name '*.php' -type f | sed 's!^\./\|^./\(var\|.cache\|vendor/bin\)/.\+$!!g; /^[[:blank:]]*$/d' | sort)
}
##
# This function reads the output of a grep command with the option -H or
# --with-filename enabled. The lines containing class and namespace declarations
# will be parsed and added to the index.
#
# shellcheck disable=SC2153
##
fillIndex() {
[[ -n $CACHE_DIR ]] || return 1
[[ -n $CLASSES ]] || return 1
[[ -n $NAMESPACES ]] || return 1
[[ -n $USES ]] || return 1
[[ -n $USES_LOOKUP ]] || return 1
[[ -n $USES_LOOKUP_OWN ]] || return 1
[[ -n $FILE_PATHS ]] || return 1
[[ -n $NAMESPACE_FILE_PATHS ]] || return 1
[[ -n $INDEXED ]] || return 1
[[ -d $CACHE_DIR ]] || mkdir -p "$CACHE_DIR"
# Clean up index files if not diffing.
if [[ ${CONFIG[$INDEX_NEW]} != '--new' ]]; then
echo > "$NAMESPACES"
echo > "$CLASSES"
echo > "$USES"
echo > "$USES_LOOKUP"
echo > "$FILE_PATHS"
echo > "$USES_OWN"
echo > "$USES_LOOKUP_OWN"
echo > "$NAMESPACE_FILE_PATHS"
echo > "$INDEXED"
fi
declare -A namespaces=() classes=()
while IFS=':' read -ra line; do
declare file="${line[0]}"
# Save the namespace or class to add to the FQN cache later on.
if [[ "${line[1]}" =~ (class|trait|interface)[[:blank:]]+([A-Za-z_]+) ]]; then
classes[$file]="${BASH_REMATCH[2]}"
elif [[ "${line[1]}" =~ namespace[[:blank:]]+([A-Za-z_\\]+) ]]; then
namespaces[$file]="${BASH_REMATCH[1]}"
else
debugf 'No class or namespace found in line "%s"' "${line[0]}"
fi
# Add filename to file with indexed filenames. This is required
# for diffing the index.
echo "$file" >> "$INDEXED"
if [[ $((++lines%500)) -eq 0 ]]; then
info "indexed $lines lines."
fi
done
# Fill up the index
declare -i uses=0
for file in "${!classes[@]}"; do
declare namespace="${namespaces[$file]}"
declare class="${classes[$file]}"
if [[ -z $class ]]; then
debugf 'Class is missing for file "%s"\n' "$file"
debugf 'Namespace: "%s"\n' "$namespace"
continue
fi
((uses++))
[[ $((uses%500)) -eq 0 ]] && info "Found FQN's for $uses classes."
echo "$namespace" >> "$NAMESPACES"
echo "$class" >> "$CLASSES"
echo "$namespace\\$class" >> "$USES"
echo "$class:$namespace\\$class" >> "$USES_LOOKUP"
echo "$file:$namespace\\$class" >> "$FILE_PATHS"
echo "$file:$namespace" >> "$NAMESPACE_FILE_PATHS"
if [[ $file != 'vendor/'* ]]; then
echo "$namespace\\$class" >> "$USES_OWN"
echo "$class:$namespace\\$class" >> "$USES_LOOKUP_OWN"
fi
done
# This keeps the index of class names unique, so that completing class names takes as little
# time as possible.
# Use echo and a subshell here to prevent changing the file before the command is done.
# shellcheck disable=SC2005
echo "$(sort -u < "$CLASSES")" > "$CLASSES"
# Ditto for the namespaces index
# shellcheck disable=SC2005
echo "$(sort -u < "$NAMESPACES")" > "$NAMESPACES"
info "Finished indexing. Indexed ${lines} lines and found FQN's for $uses classes." >&2
}
checkCache() {
if ! [[ -d "$CACHE_DIR" ]]; then
info "No cache dir found, indexing." >&2
execute index
fi
}
##
# Find use statements and needed classes in a file.
findUsesAndNeeds() {
declare -p needs &>>/dev/null || return 1
declare -p uses &>>/dev/null || return 1
# shellcheck disable=SC2154
declare -p namespace &>>/dev/null || return 1
while read -r line; do
[[ $line == namespace* ]] && check_uses='true'
if [[ $line == ?(@(abstract|final) )@(class|interface|trait)* ]]; then
check_uses='false'
check_needs='true'
read -ra line_array <<<"$line"
set -- "${line_array[@]}"
while shift && [[ "$1" != @(extends|implements) ]]; do :; done;
while shift && [[ -n $1 ]]; do
[[ $1 == 'implements' ]] && shift
[[ $1 == \\* ]] || _set_needed_if_not_used "$1"
done
fi
if $check_uses; then
if [[ $line == use* ]]; then
declare class_name="${line##*\\}"
[[ $class_name == *as* ]] && class_name="${class_name##*as }"
debug "Class name: $class_name"
class_name="${class_name%%[^a-zA-Z]*}"
uses["$class_name"]='used'
fi
fi
if $check_needs; then
if [[ $line == *function*([[:space:]])*([[:alnum:]_])\(* ]]; then
_check_function_needs "$line"
continue
fi
_check_needs "$line"
fi
done
}
_check_function_needs() {
# Strip everything up until function name and argument declaration.
declare line="${1#*function}" function_declaration="${1#*function}"
# Collect the entire argument declaration
while [[ $line != *'{'* ]] && read -r line; do
function_declaration="$function_declaration $line"
done
declare -a words=()
read -ra words <<<"$function_declaration"
for i in "${!words[@]}"; do
if [[ "${words[$i]}" =~ ^'$'[a-zA-Z_]+ ]]; then
declare prev_word="${words[$((i-1))]}"
if [[ $prev_word =~ ^([^\(]*\()?([A-Za-z]+)$ ]]; then
declare class_name="${BASH_REMATCH[2]}"
debugf 'Found parameter type "%s" for function "%s"\n' "$class_name" "$function_declaration"
_set_needed_if_not_used "$class_name"
fi
fi
done
if [[ "$function_declaration" =~ \):[[:space:]]+([a-zA-Z]+) ]]; then
declare class_name="${BASH_REMATCH[1]}"
debugf 'Found return type "%s" for function "%s"\n' "$class_name" "$function_declaration"
_set_needed_if_not_used "$class_name"
fi
}
_check_needs() {
declare line="$1" match=''
if _line_matches "$line"; then
declare class_name="${match//[^a-zA-Z]/}"
debugf 'Extracted type "%s" from line "%s". Entire match: "%s"\n' "$class_name" "$line" "${BASH_REMATCH[0]}"
_set_needed_if_not_used "$class_name"
line="${line/"${BASH_REMATCH[0]/}"}"
_check_needs "$line"
fi
}
# shellcheck disable=SC2049
_line_matches() {
if [[ $line =~ 'new'[[:space:]]+([^\\][A-Za-z]+)\( ]] \
|| [[ $line =~ 'instanceof'[[:space:]]+([A-Za-z]+) ]] \
|| [[ $line =~ catch[[:space:]]*\(([A-Za-z]+) ]] \
|| [[ $line =~ \*[[:blank:]]*@([A-Z][a-zA-Z]*) ]]; then
match="${BASH_REMATCH[1]}"
return $?
elif [[ $line =~ @(var|param|return|throws)[[:space:]]+([A-Za-z]+) ]] \
|| [[ $line =~ (^|[\(\[\{[:blank:]])([A-Za-z]+)'::' ]]; then
match="${BASH_REMATCH[2]}"
return $?
fi
return 1
}
_set_needed_if_not_used() {
declare class_name="$1"
if [[ -z ${uses["$class_name"]} ]] \
&& [[ -z ${namespace["$class_name"]} ]] \
&& [[ "$class_name" != @(static|self|string|int|float|array|object|bool|mixed|parent|void) ]]; then
needs["$class_name"]='needed'
fi
}
execute "$@"

@ -1,519 +0,0 @@
;;; phpinspect-index.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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 'cl-lib)
(require 'phpinspect-util)
(require 'phpinspect-type)
(require 'phpinspect-token-predicates)
(require 'phpinspect-parser)
(defun phpinspect--function-from-scope (scope)
(cond ((and (phpinspect-static-p (cadr scope))
(phpinspect-function-p (caddr scope)))
(caddr scope))
((phpinspect-function-p (cadr scope))
(cadr scope))
(t nil)))
(defun phpinspect--index-function-arg-list (type-resolver arg-list &optional add-used-types)
(let ((arg-index)
(current-token)
(arg-list (cl-copy-list arg-list)))
(while (setq current-token (pop arg-list))
(cond ((and (phpinspect-word-p current-token)
(phpinspect-variable-p (car arg-list)))
(push `(,(cadr (pop arg-list))
,(funcall type-resolver (phpinspect--make-type :name (cadr current-token))))
arg-index)
(when add-used-types (funcall add-used-types (list (cadr current-token)))))
((phpinspect-variable-p (car arg-list))
(push `(,(cadr (pop arg-list))
nil)
arg-index))))
(nreverse arg-index)))
(defsubst phpinspect--should-prefer-return-annotation (type)
"Returns non-nil if return annotation should supersede typehint
of TYPE, if available."
(or (not type)
(phpinspect--type= type phpinspect--object-type)))
(defun phpinspect--index-function-declaration (declaration type-resolver add-used-types)
(let (current name function-args return-type)
(catch 'break
(while (setq current (pop declaration))
(cond ((and (phpinspect-word-p current)
(phpinspect-word-p (car declaration))
(string= "function" (cadr current)))
(setq name (cadr (pop declaration))))
((phpinspect-list-p current)
(setq function-args
(phpinspect--index-function-arg-list
type-resolver current add-used-types))
(when (setq return-type (seq-find #'phpinspect-word-p declaration))
(setq return-type (funcall type-resolver
(phpinspect--make-type :name (cadr return-type)))))
(throw 'break nil)))))
(list name function-args return-type)))
(defun phpinspect--index-function-from-scope (type-resolver scope comment-before &optional add-used-types namespace)
"Index a function inside SCOPE token using phpdoc metadata in COMMENT-BEFORE.
If ADD-USED-TYPES is set, it must be a function and will be
called with a list of the types that are used within the
function (think \"new\" statements, return types etc.)."
(phpinspect--log "Indexing function")
(let* ((php-func (cadr scope))
(declaration (cadr php-func))
name type arguments)
(pcase-setq `(,name ,arguments ,type)
(phpinspect--index-function-declaration
declaration type-resolver add-used-types))
;; FIXME: Anonymous functions should not be indexed! (or if they are, they
;; should at least not be visible from various UIs unless assigned to a
;; variable as a closure).
(unless name (setq name "anonymous"))
(phpinspect--log "Checking function return annotations")
;; @return annotation. When dealing with a collection, we want to store the
;; type of its members.
(let* ((return-annotation-type
(cadadr (seq-find #'phpinspect-return-annotation-p comment-before)))
(is-collection
(and type
(phpinspect--type-is-collection type))))
(phpinspect--log "found return annotation %s in %s when type is %s"
return-annotation-type comment-before type)
(unless (stringp return-annotation-type)
(phpinspect--log "Discarding invalid return annotation type %s" return-annotation-type)
(setq return-annotation-type nil))
(when return-annotation-type
(when (string-suffix-p "[]" return-annotation-type)
(setq is-collection t)
(setq return-annotation-type (string-trim-right return-annotation-type "\\[\\]")))
(cond ((phpinspect--should-prefer-return-annotation type)
(setq type (funcall type-resolver
(phpinspect--make-type :name return-annotation-type))))
(is-collection
(phpinspect--log "Detected collection type in: %s" scope)
(setf (phpinspect--type-contains type)
(funcall type-resolver
(phpinspect--make-type :name return-annotation-type)))
(setf (phpinspect--type-collection type) t)))))
(when add-used-types
(let ((used-types (phpinspect--find-used-types-in-tokens
`(,(seq-find #'phpinspect-block-p php-func)))))
(when type (push (phpinspect--type-bare-name type) used-types))
(funcall add-used-types used-types)))
(phpinspect--log "Creating function object")
(phpinspect--make-function
:scope `(,(car scope))
:token php-func
:name (concat (if namespace (concat namespace "\\") "") name)
:return-type (or type phpinspect--null-type)
:arguments arguments)))
(define-inline phpinspect--safe-cadr (list)
(inline-letevals (list)
(inline-quote
(when (listp ,list) (cadr ,list)))))
(defun phpinspect--index-const-from-scope (scope)
(phpinspect--make-variable
:scope `(,(car scope))
:mutability `(,(caadr scope))
:name (phpinspect--safe-cadr (phpinspect--safe-cadr (phpinspect--safe-cadr scope)))))
(defun phpinspect--var-annotations-from-token (token)
(seq-filter #'phpinspect-var-annotation-p token))
(defun phpinspect--variable-type-string-from-comment (comment variable-name)
(let* ((var-annotations (phpinspect--var-annotations-from-token comment))
(type (if var-annotations
;; Find the right annotation by variable name
(or (cadr (cadr (seq-find (lambda (annotation)
(string= (cadr (caddr annotation)) variable-name))
var-annotations)))
;; Give up and just use the last one encountered
(cadr (cadr (car (last var-annotations))))))))
;; If type is not a string, the annotation is probably invalid and we should
;; return nil.
(when (stringp type) type)))
(defun phpinspect--index-variable-from-scope (type-resolver scope comment-before &optional static)
"Index the variable inside `scope`."
(let* ((variable-name (cadr (cadr scope)))
(type
(phpinspect--variable-type-string-from-comment comment-before variable-name)))
(phpinspect--log "calling resolver from index-variable-from-scope")
(phpinspect--make-variable
;; Static class variables are always prefixed with dollar signs when
;; referenced.
:name (if static (concat "$" variable-name) variable-name)
:scope `(,(car scope))
:lifetime (when static '(:static))
:type (if type (funcall type-resolver (phpinspect--make-type :name type))))))
(defun phpinspect-doc-block-p (token)
(phpinspect-token-type-p token :doc-block))
(defsubst phpinspect--index-method-annotations (type-resolver comment)
(let ((annotations (seq-filter #'phpinspect-method-annotation-p comment))
(methods))
(dolist (annotation annotations)
(let ((return-type) (name) (arg-list))
(when (> (length annotation) 2)
(cond ((and (phpinspect-word-p (nth 1 annotation))
(phpinspect-word-p (nth 2 annotation))
(phpinspect-list-p (nth 3 annotation)))
(setq return-type (cadr (nth 1 annotation)))
(setq name (cadr (nth 2 annotation)))
(setq arg-list (nth 3 annotation)))
((and (phpinspect-word-p (nth 1 annotation))
(phpinspect-list-p (nth 2 annotation)))
(setq return-type "void")
(setq name (cadr (nth 1 annotation)))
(setq arg-list (nth 2 annotation))))
(when name
(push (phpinspect--make-function
:scope '(:public)
:name name
:return-type (funcall type-resolver (phpinspect--make-type :name return-type))
:arguments (phpinspect--index-function-arg-list type-resolver arg-list))
methods)))))
methods))
(defun phpinspect--index-class (imports type-resolver location-resolver class &optional doc-block)
"Create an alist with relevant attributes of a parsed class."
(phpinspect--log "INDEXING CLASS")
(let ((methods)
(static-methods)
(static-variables)
(variables)
(constants)
(extends)
(implements)
(class-name)
;; Keep track of encountered comments to be able to use type
;; annotations.
(comment-before)
;; The types that are used within the code of this class' methods.
(used-types)
(add-used-types))
(setq add-used-types
(lambda (additional-used-types)
(if used-types
(nconc used-types additional-used-types)
(setq used-types additional-used-types))))
(pcase-setq `(,class-name ,extends ,implements ,used-types)
(phpinspect--index-class-declaration (cadr class) type-resolver))
(dolist (token (caddr class))
(cond ((phpinspect-scope-p token)
(cond ((phpinspect-const-p (cadr token))
(push (phpinspect--index-const-from-scope token) constants))
((phpinspect-variable-p (cadr token))
(push (phpinspect--index-variable-from-scope type-resolver
token
comment-before)
variables))
((phpinspect-static-p (cadr token))
(cond ((phpinspect-function-p (cadadr token))
(push (phpinspect--index-function-from-scope type-resolver
(list (car token)
(cadadr token))
comment-before
add-used-types)
static-methods))
((phpinspect-variable-p (cadadr token))
(push (phpinspect--index-variable-from-scope type-resolver
(list (car token)
(cadadr token))
comment-before
'static)
static-variables))))
(t
(phpinspect--log "comment-before is: %s" comment-before)
(push (phpinspect--index-function-from-scope type-resolver
token
comment-before
add-used-types)
methods))))
((phpinspect-static-p token)
(cond ((phpinspect-function-p (cadr token))
(push (phpinspect--index-function-from-scope type-resolver
`(:public
,(cadr token))
comment-before
add-used-types)
static-methods))
((phpinspect-variable-p (cadr token))
(push (phpinspect--index-variable-from-scope type-resolver
`(:public
,(cadr token))
comment-before)
static-variables))))
((phpinspect-const-p token)
;; Bare constants are always public
(push (phpinspect--index-const-from-scope (list :public token))
constants))
((phpinspect-function-p token)
;; Bare functions are always public
(push (phpinspect--index-function-from-scope type-resolver
(list :public token)
comment-before
add-used-types)
methods))
((phpinspect-doc-block-p token)
(phpinspect--log "setting comment-before %s" token)
(setq comment-before token))
;; Prevent comments from sticking around too long
(t
(phpinspect--log "Unsetting comment-before")
(setq comment-before nil))))
;; Dirty hack that assumes the constructor argument names to be the same as the object
;; attributes' names.
;;;
;; TODO: actually check the types of the variables assigned to object attributes
(let* ((constructor-sym (phpinspect-intern-name "__construct"))
(constructor (seq-find (lambda (method)
(eq (phpinspect--function-name-symbol method)
constructor-sym))
methods)))
(when constructor
(phpinspect--log "Constructor was found")
(dolist (variable variables)
(when (not (phpinspect--variable-type variable))
(phpinspect--log "Looking for variable type in constructor arguments (%s)"
variable)
(let ((constructor-parameter-type
(car (alist-get (phpinspect--variable-name variable)
(phpinspect--function-arguments constructor)
nil nil #'string=))))
(if constructor-parameter-type
(setf (phpinspect--variable-type variable)
(funcall type-resolver constructor-parameter-type))))))))
;; Add method annotations to methods
(when doc-block
(setq methods
(nconc methods (phpinspect--index-method-annotations type-resolver doc-block))))
`(,class-name .
(phpinspect--indexed-class
(complete . ,(not (phpinspect-incomplete-class-p class)))
(class-name . ,class-name)
(declaration . ,(seq-find #'phpinspect-declaration-p class))
(location . ,(funcall location-resolver class))
(imports . ,imports)
(methods . ,methods)
(static-methods . ,static-methods)
(static-variables . ,static-variables)
(variables . ,variables)
(constants . ,constants)
(extends . ,extends)
(implements . ,implements)
(used-types . ,(mapcar #'phpinspect-intern-name
(seq-uniq used-types #'string=)))))))
(defsubst phpinspect-namespace-body (namespace)
"Return the nested tokens in NAMESPACE tokens' body.
Accounts for namespaces that are defined with '{}' blocks."
(if (phpinspect-block-p (caddr namespace))
(cdaddr namespace)
(cdr namespace)))
(defun phpinspect--index-classes-in-tokens
(imports tokens type-resolver-factory location-resolver &optional namespace)
"Index the class tokens among TOKENS.
NAMESPACE will be assumed the root namespace if not provided"
(let ((comment-before)
(indexed))
(dolist (token tokens)
(cond ((phpinspect-doc-block-p token)
(setq comment-before token))
((phpinspect-class-p token)
(push (phpinspect--index-class
imports (funcall type-resolver-factory imports token namespace)
location-resolver token comment-before)
indexed)
(setq comment-before nil))))
indexed))
(defun phpinspect--index-namespace (namespace type-resolver-factory location-resolver)
(let* (used-types
(index
`((classes . ,(phpinspect--index-classes-in-tokens
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
namespace
type-resolver-factory location-resolver (cadadr namespace)))
(functions . ,(phpinspect--index-functions-in-tokens
namespace
type-resolver-factory
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
(cadadr namespace)
(lambda (types) (setq used-types (nconc used-types types))))))))
(push `(used-types . ,used-types) index)))
(defun phpinspect--index-namespaces
(namespaces type-resolver-factory location-resolver &optional indexed)
(if namespaces
(let ((namespace-index
(phpinspect--index-namespace
(pop namespaces) type-resolver-factory location-resolver)))
(if indexed
(progn
(nconc (alist-get 'used-types indexed)
(alist-get 'used-types namespace-index))
(nconc (alist-get 'classes indexed)
(alist-get 'classes namespace-index))
(nconc (alist-get 'functions indexed)
(alist-get 'functions namespace-index)))
(setq indexed namespace-index))
(phpinspect--index-namespaces
namespaces type-resolver-factory location-resolver indexed))
indexed))
(defun phpinspect--index-functions-in-tokens (tokens type-resolver-factory &optional imports namespace add-used-types)
"TODO: implement function indexation. This is a stub function."
(let ((type-resolver (funcall type-resolver-factory imports nil namespace))
comment-before functions)
(dolist (token tokens)
(cond ((phpinspect-comment-p token)
(setq comment-before token))
((phpinspect-function-p token)
(push (phpinspect--index-function-from-scope
type-resolver `(:public ,token) comment-before add-used-types
namespace)
functions))))
functions))
(defun phpinspect--find-used-types-in-tokens (tokens)
"Find usage of the \"new\" keyword in TOKENS.
Return value is a list of the types that are \"newed\"."
(let* ((previous-tokens)
(used-types (cons nil nil))
(used-types-rear used-types))
(while tokens
(let ((token (pop tokens))
(previous-token (car previous-tokens)))
(cond ((and (phpinspect-word-p previous-token)
(string= "new" (cadr previous-token))
(phpinspect-word-p token))
(let ((type (cadr token)))
(when (not (string-match-p "\\\\" type))
(setq used-types-rear (setcdr used-types-rear (cons type nil))))))
((and (phpinspect-static-attrib-p token)
(phpinspect-word-p previous-token))
(let ((type (cadr previous-token)))
(when (not (string-match-p "\\\\" type))
(setq used-types-rear (setcdr used-types-rear (cons type nil))))))
((phpinspect-object-attrib-p token)
(let ((lists (seq-filter #'phpinspect-list-p token)))
(dolist (list lists)
(setq used-types-rear
(nconc used-types-rear
(phpinspect--find-used-types-in-tokens (cdr list)))
used-types-rear (last used-types-rear)))))
((or (phpinspect-list-p token) (phpinspect-block-p token))
(setq used-types-rear
(nconc used-types-rear (phpinspect--find-used-types-in-tokens (cdr token)))
used-types-rear (last used-types-rear))))
(push token previous-tokens)))
(cdr used-types)))
(defun phpinspect--index-tokens (tokens &optional type-resolver-factory location-resolver)
"Index TOKENS as returned by `phpinspect--parse-current-buffer`."
(or
(condition-case-unless-debug err
(progn
(unless type-resolver-factory
(setq type-resolver-factory #'phpinspect--make-type-resolver))
(unless location-resolver
(setq location-resolver (lambda (_) (list 0 0))))
(let* ((imports (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p tokens)))
(namespace-index
(phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-p tokens)
type-resolver-factory
location-resolver)))
`(phpinspect--root-index
(imports . ,imports)
(classes ,@(append
(alist-get 'classes namespace-index)
(phpinspect--index-classes-in-tokens
imports tokens type-resolver-factory location-resolver)))
(used-types ,@(mapcar #'phpinspect-intern-name
(seq-uniq
(append
(alist-get 'used-types namespace-index)
(phpinspect--find-used-types-in-tokens tokens))
#'string=)))
(functions . ,(append
(alist-get 'functions namespace-index)
(phpinspect--index-functions-in-tokens
tokens type-resolver-factory imports))))))
(t
(phpinspect--log "phpinspect--index-tokens failed: %s. Enable debug-on-error for backtrace." err)
nil))
'(phpinspect--root-index)))
(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)))
(provide 'phpinspect-index)
;;; phpinspect-index.el ends here

@ -1,242 +0,0 @@
;;; phpinspect-meta.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-splayt)
(define-inline phpinspect-make-meta
(parent start end whitespace-before token &optional overlay children parent-offset)
(inline-quote (list 'meta ,parent ,start ,end ,whitespace-before ,token ,overlay
;;,children ,parent-offset)))
(or ,children (phpinspect-make-splayt)) ,parent-offset)))
(define-inline phpinspect-meta-parent (meta)
(inline-quote (cadr ,meta)))
(define-inline phpinspect-meta-children (meta)
(inline-quote (car (nthcdr 7 ,meta))))
(define-inline phpinspect-meta-parent-offset (meta)
(inline-quote (car (nthcdr 8 ,meta))))
(define-inline phpinspect-meta-overlay (meta)
(inline-quote (car (nthcdr 6 ,meta))))
(define-inline phpinspect-meta-token (meta)
(inline-quote (car (nthcdr 5 ,meta))))
(define-inline phpinspect-meta-absolute-end (meta)
(inline-quote (cadddr ,meta)))
(define-inline phpinspect-meta-whitespace-before (meta)
(inline-quote (car (cddddr ,meta))))
(define-inline phpinspect-meta-parent-start (meta)
"Calculate parent start position iteratively based on parent offsets."
(inline-letevals (meta)
(inline-quote
(let ((start (or (phpinspect-meta-parent-offset ,meta) 0))
(current ,meta))
(while (phpinspect-meta-parent current)
(setq current (phpinspect-meta-parent current)
start (+ start (or (phpinspect-meta-parent-offset current) 0))))
(+ (phpinspect-meta-absolute-start current) start)))))
(define-inline phpinspect-meta-start (meta)
"Calculate the start position of META."
(inline-quote
(if (phpinspect-meta-parent ,meta)
(+ (phpinspect-meta-parent-start (phpinspect-meta-parent ,meta))
(phpinspect-meta-parent-offset ,meta))
(phpinspect-meta-absolute-start ,meta))))
(define-inline phpinspect-meta-width (meta)
(inline-letevals (meta)
(inline-quote
(- (phpinspect-meta-absolute-end ,meta) (phpinspect-meta-absolute-start ,meta)))))
(define-inline phpinspect-meta-end (meta)
(inline-letevals (meta)
(inline-quote
(+ (phpinspect-meta-start ,meta) (phpinspect-meta-width ,meta)))))
(defsubst phpinspect-meta-find-root (meta)
(while (phpinspect-meta-parent meta)
(setq meta (phpinspect-meta-parent meta)))
meta)
(defun phpinspect-meta-sort-width (meta1 meta2)
(< (phpinspect-meta-width meta1) (phpinspect-meta-width meta2)))
(defun phpinspect-meta-sort-start (meta1 meta2)
(< (phpinspect-meta-start meta1) (phpinspect-meta-start meta2)))
(define-inline phpinspect-meta-absolute-start (meta)
(inline-quote (caddr ,meta)))
(define-inline phpinspect-meta-overlaps-point (meta point)
"Check if META's region overlaps POINT."
(inline-letevals (point meta)
(inline-quote
(and (> (phpinspect-meta-end ,meta) ,point)
(<= (phpinspect-meta-start ,meta) ,point)))))
(defun 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))))))
(define-inline phpinspect-meta-set-parent (meta parent)
(inline-letevals (meta parent)
(inline-quote
(progn
(when ,parent
(setf (phpinspect-meta-parent-offset ,meta)
(- (phpinspect-meta-start ,meta) (phpinspect-meta-start ,parent)))
(phpinspect-meta-add-child ,parent ,meta))
(setcar (cdr ,meta) ,parent)
,meta))))
;; Note: using defsubst here causes a byte code overflow
(defun phpinspect-meta-add-child (meta child)
(phpinspect-splayt-insert (phpinspect-meta-children meta) (phpinspect-meta-parent-offset child) child))
(define-inline phpinspect-meta-detach-parent (meta)
(inline-letevals (meta)
(inline-quote
(when (phpinspect-meta-parent ,meta)
;; Update absolute start and end
(setf (phpinspect-meta-absolute-end ,meta) (phpinspect-meta-end ,meta))
;; Note: start should always be updated after end, as end is derived from
;; it.
(setf (phpinspect-meta-absolute-start ,meta) (phpinspect-meta-start ,meta))
(setf (phpinspect-meta-parent ,meta) nil)))))
(defun phpinspect-meta-shift (meta delta)
(setf (phpinspect-meta-absolute-start meta) (+ (phpinspect-meta-start meta) delta))
(setf (phpinspect-meta-absolute-end meta) (+ (phpinspect-meta-end meta) delta)))
(defun phpinspect-meta-right-siblings (meta)
(sort
(phpinspect-splayt-find-all-after
(phpinspect-meta-children (phpinspect-meta-parent meta)) (phpinspect-meta-parent-offset meta))
#'phpinspect-meta-sort-start))
(defun phpinspect-meta-wrap-token-pred (predicate)
(lambda (meta) (funcall predicate (phpinspect-meta-token meta))))
(define-inline phpinspect-meta--point-offset (meta point)
(inline-quote
(- ,point (phpinspect-meta-start ,meta))))
(cl-defmethod phpinspect-meta-find-left-sibling ((meta (head meta)))
(when (phpinspect-meta-parent meta)
(phpinspect-splayt-find-largest-before (phpinspect-meta-children (phpinspect-meta-parent meta))
(phpinspect-meta-parent-offset meta))))
(cl-defmethod phpinspect-meta-find-right-sibling ((meta (head meta)))
(when (phpinspect-meta-parent meta)
(phpinspect-splayt-find-smallest-after (phpinspect-meta-children (phpinspect-meta-parent meta))
(phpinspect-meta-parent-offset meta))))
(cl-defmethod phpinspect-meta-find-overlapping-child ((meta (head meta)) (point integer))
(let ((child (phpinspect-splayt-find-largest-before
(phpinspect-meta-children meta)
;; Use point +1 as a child starting at point still overlaps
(+ (phpinspect-meta--point-offset meta point) 1))))
(when (and child (phpinspect-meta-overlaps-point child point))
child)))
(cl-defmethod phpinspect-meta-find-overlapping-children ((meta (head meta)) (point integer))
(let ((child meta)
children)
(while (and (setq child (phpinspect-meta-find-overlapping-child child point))
(phpinspect-meta-overlaps-point child point))
(push child children))
children))
(cl-defmethod phpinspect-meta-find-child-starting-at ((meta (head meta)) (point integer))
(phpinspect-splayt-find (phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point)))
(cl-defmethod phpinspect-meta-find-child-starting-at-recursively ((meta (head meta)) (point integer))
(let ((child (phpinspect-meta-find-child-starting-at meta point)))
(if child
child
(setq child (phpinspect-meta-find-overlapping-child meta point))
(when child
(phpinspect-meta-find-child-starting-at-recursively child point)))))
(cl-defmethod phpinspect-meta-find-child-before ((meta (head meta)) (point integer))
(phpinspect-splayt-find-largest-before
(phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point)))
(cl-defmethod phpinspect-meta-find-child-after ((meta (head meta)) (point integer))
(phpinspect-splayt-find-smallest-after
(phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point)))
(cl-defmethod phpinspect-meta-find-child-before-recursively ((meta (head meta)) (point integer))
(let ((child meta)
last)
(while (setq child (phpinspect-meta-find-child-before child point))
(setq last child))
last))
(cl-defmethod phpinspect-meta-find-children-after ((meta (head meta)) (point integer))
(sort
(phpinspect-splayt-find-all-after
(phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point))
#'phpinspect-meta-sort-start))
(cl-defmethod phpinspect-meta-find-children-before ((meta (head meta)) (point integer))
(sort (phpinspect-splayt-find-all-before
(phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point))
#'phpinspect-meta-sort-start))
(cl-defmethod phpinspect-meta-find-first-child-matching ((meta (head meta)) predicate)
(catch 'return
(phpinspect-splayt-traverse-lr (child (phpinspect-meta-children meta))
(when (funcall predicate child)
(throw 'return child)))))
(cl-defmethod phpinspect-meta-last-child ((meta (head meta)))
(phpinspect-meta-find-child-before meta (phpinspect-meta-end meta)))
(cl-defmethod phpinspect-meta-first-child ((meta (head meta)))
(phpinspect-meta-find-child-after meta (- (phpinspect-meta-start meta) 1)))
(defun phpinspect-meta-string (meta)
(if meta
(format "[start: %d, end: %d, token: %s]"
(phpinspect-meta-start meta) (phpinspect-meta-end meta) (phpinspect-meta-token meta))
"[nil]"))
(provide 'phpinspect-meta)
;;; phpinspect-meta.el ends here

@ -1,138 +0,0 @@
;;; phpinspect-parse-context.el --- PHP parsing context module -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-util)
(require 'phpinspect-meta)
(require 'phpinspect-changeset)
(defvar phpinspect-parse-context nil
"An instance of `phpinspect-pctx' that is used when
parsing. Usually used in combination with
`phpinspect-with-parse-context'")
(cl-defstruct (phpinspect-pctx (:constructor phpinspect-make-pctx))
"Parser Context"
(incremental nil)
(meta-iterator nil)
(interrupt-threshold (time-convert '(0 0 2000 0) t)
:documentation
"After how much time `interrupt-predicate'
should be polled. This is 2ms by default.")
(-start-time nil
:documentation "The time at which the parse started.
This variable is for private use and not always set.")
(interrupt-predicate nil
:documentation
"A function that is called in intervals during parsing when
set. If this function returns a non-nil value, the parse process
is interrupted and the symbol `phpinspect-parse-interrupted' is
thrown.")
(changesets nil
:type list
:documentation "Metadata change sets executed during this parse")
(edtrack nil
:type phpinspect-edtrack)
(bmap nil
:type phpinspect-bmap)
(previous-bmap nil
:type phpinspect-bmap)
(whitespace-before ""
:type string))
(defmacro phpinspect-with-parse-context (ctx &rest body)
(declare (indent 1))
(let ((old-ctx (gensym))
(completed (gensym))
(result (gensym)))
`(let ((,old-ctx phpinspect-parse-context)
(,result)
(,completed))
(unwind-protect
(progn
(setq phpinspect-parse-context ,ctx
,result (progn ,@body)
,completed t)
,result)
(progn
(unless ,completed (phpinspect-pctx-cancel ,ctx))
(setq phpinspect-parse-context ,old-ctx))))))
(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)))))
(define-inline phpinspect-pctx-register-changeset (pctx changeset)
(inline-quote
(progn
(push ,changeset (phpinspect-pctx-changesets ,pctx)))))
(define-inline phpinspect-meta-with-changeset (meta &rest body)
(declare (indent 1))
(inline-letevals (meta)
(push 'progn body)
(inline-quote
(progn
(when phpinspect-parse-context
(phpinspect-pctx-register-changeset
phpinspect-parse-context (phpinspect-make-changeset ,meta)))
,body))))
(define-inline phpinspect-pctx-check-interrupt (pctx)
(inline-letevals (pctx)
(inline-quote
(progn
(unless (phpinspect-pctx--start-time ,pctx)
(setf (phpinspect-pctx--start-time ,pctx) (time-convert nil t)))
;; Interrupt when blocking too long while input is pending.
(when (and (time-less-p (phpinspect-pctx-interrupt-threshold ,pctx)
(time-since (phpinspect-pctx--start-time ,pctx)))
(funcall (phpinspect-pctx-interrupt-predicate ,pctx)))
(phpinspect-pctx-cancel ,pctx)
(throw 'phpinspect-parse-interrupted nil))))))
(define-inline phpinspect-pctx-register-whitespace (pctx whitespace)
(inline-quote
(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-pctx-cancel (pctx)
(phpinspect--log "Cancelling parse context")
(dolist (changeset (phpinspect-pctx-changesets pctx))
(phpinspect-changeset-revert changeset))
(setf (phpinspect-pctx-changesets pctx) nil))
(provide 'phpinspect-parse-context)
;;; phpinspect-parse-context.el ends here

@ -1,883 +0,0 @@
;;; phpinspect-parser.el --- PHP parsing module -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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 'cl-lib)
(require 'phpinspect-edtrack)
(require 'phpinspect-bmap)
(require 'phpinspect-meta)
(require 'phpinspect-parse-context)
(require 'phpinspect-token-predicates)
(eval-when-compile
(define-inline phpinspect--word-end-regex ()
(inline-quote "\\([[:blank:]]\\|[^0-9a-zA-Z_]\\)")))
(define-inline phpinspect--strip-word-end-space (string)
(inline-letevals (string)
(inline-quote
(progn
(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)
"Return a token of type TOKEN-KEYWORD with STRING as value.
If STRING has text properties, they are stripped."
(let ((value (copy-sequence string))
(length (length string)))
(forward-char length)
(set-text-properties 0 length nil value)
(list token-keyword value)))
(eval-and-compile
(defun phpinspect-handler-func-name (handler-name)
(intern (concat "phpinspect--" (symbol-name handler-name) "-handler")))
(defun phpinspect-handler-regexp-func-name (handler-name)
(intern (concat "phpinspect--" (symbol-name handler-name) "-handler-regexp")))
(defun phpinspect-parser-func-name (name &optional suffix)
(intern (concat "phpinspect--parse-" (symbol-name name) (if suffix (concat "-" suffix) "")))))
(defmacro phpinspect-defhandler (name arguments docstring attribute-alist &rest body)
"Define a parser handler that becomes available for use with phpinspect-parse.
A parser handler is a function that is able to identify and parse
tokens from PHP code at `point` in the current buffer. It's
return value must be the resulting token. Aside from parsing it
has to manage the state of `point` in a way that it skips over
the tokens it has parsed. That way the next handler can
correctly pick up from where it has left off.
Parser handlers are unrolled in a `cond` statement by
`phpinspect-make-parser-function` and equivalents. The resulting
code is something akin to the following:
(while ...
(cond (((looking-at \"{\")
(funcall block-handler (match-string 0) max-point)
((looking-at \"\\$\")
(funcall variable-handler ...
etc.
NAME must be a symbol. It does not need to be prefixed with a
\"namespace\" because parser handlers are automatically prefixed.
ARGUMENTS must an argument list as accepted by `lambda`. A
handler must be able to accept 2 arguments: START-TOKEN and
MAX-POINT. START-TOKEN is the match string that resulted from
the comparison of the handlers' `regexp` attribute with the text
at `point`. MAX-POINT is the point in the current buffer up
until which the parser is supposed to parse. For some tokens you
may not want/need to respect MAX-POINT, in which case you can
ignore it.
DOCSTRING is mandatory. It should contain a description of the
tokens the handler is able to process and (if present) any
particularities of the handler.
ATTRIBUTE-ALIST is an alist that must contain at least a `regexp` key.
Possible keys:
- regexp: The regular expression that marks the start of the token.
BODY is a function body as accepted by `lambda` that parses the
text at point and returns the resulting token.
When altering/adding handlers during runtime, make sure to purge
the parser cache to make sure that your new handler functions are used.
You can purge the parser cache with \\[phpinspect-purge-parser-cache]."
(declare (indent defun))
(when (not (symbolp name))
(error "In definition of phpinspect handler %s: NAME bust be a symbol" name))
(when (not (alist-get 'regexp attribute-alist))
(error "In definition of phpinspect handler %s ATTRIBUTE-PLIST must contain key `regexp`"
name))
(let ((regexp-inline-name (phpinspect-handler-regexp-func-name name))
(inline-name (phpinspect-handler-func-name name)))
`(progn
(define-inline ,regexp-inline-name ()
(inline-letevals ((regexp ,(alist-get 'regexp attribute-alist)))
(inline-quote ,(quote ,regexp))))
(defsubst ,inline-name (,@arguments)
,docstring
,@body)
(put (quote ,inline-name) 'phpinspect--handler t))))
(eval-when-compile
(defun phpinspect-make-parser-function (name tree-type handlers &optional delimiter-predicate)
"Create a parser function using the handlers by names defined in HANDLER-LIST.
See also `phpinspect-defhandler`.
TREE-TYPE must be a symbol or a keyword representing the token
type.
HANDLERS must be a list of symbols referring to existing
parser handlers defined using `phpinspect-defhandler'.
DELIMITER-PREDICATE must be a function. It is passed the last
parsed token after every handler iteration. If it returns
something other than nil, parsing is deemed completed and the
loop exits. An example use case of this is to determine the end
of a statement. You can use `phpinspect-terminator-p` as
delimiter predicate and have parsing stop when the last parsed
token is \";\", which marks the end of a statement in PHP."
(cl-assert (symbolp delimiter-predicate))
`(defun ,(phpinspect-parser-func-name name "simple") (buffer max-point &optional skip-over continue-condition &rest _ignored)
(with-current-buffer buffer
(let* ((tokens (cons ,tree-type nil))
(tokens-rear tokens)
token)
(when skip-over (forward-char skip-over))
(while (and (< (point) max-point)
(if continue-condition (funcall continue-condition) t)
(not ,(if delimiter-predicate
`(,delimiter-predicate (car (last tokens)))
nil)))
(cond ,@(mapcar
(lambda (handler)
`((looking-at (,(phpinspect-handler-regexp-func-name handler)))
(setq token (,(phpinspect-handler-func-name handler) (match-string 0) max-point))
(when token
(setq tokens-rear (setcdr tokens-rear (cons token nil))))))
handlers)
(t (forward-char))))
;; Return
tokens))))
(defun phpinspect-make-incremental-parser-function (name tree-type handlers &optional delimiter-predicate)
"Like `phpinspect-make-parser-function', but returned function
is able to reuse an already parsed tree."
(cl-assert (symbolp delimiter-predicate))
`(defun ,(phpinspect-parser-func-name name "incremental") (context buffer max-point &optional skip-over continue-condition root)
(with-current-buffer buffer
(let* ((tokens (cons ,tree-type nil))
(tokens-rear 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)))
(check-interrupt (phpinspect-pctx-interrupt-predicate context))
;; Loop variables
(start-position)
(original-position)
(current-end-position)
(existing-meta)
(delta)
(token))
(when skip-over (forward-char skip-over))
(phpinspect-pctx-save-whitespace context
(while (and (< (point) max-point)
(if continue-condition (funcall continue-condition) t)
(not ,(if delimiter-predicate
`(,delimiter-predicate (car (last tokens)))
nil)))
(when check-interrupt
(phpinspect-pctx-check-interrupt context))
(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 delta (- start-position original-position)
current-end-position (+ (phpinspect-meta-end existing-meta) delta)
token (phpinspect-meta-token existing-meta))
;;(message "Reusing token %s at point %s" (phpinspect-meta-string existing-meta) (point))
;; Re-register existing token
(phpinspect-bmap-overlay
bmap previous-bmap existing-meta delta
(phpinspect-pctx-consume-whitespace context))
(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))
(,(phpinspect-handler-func-name 'whitespace) (match-string 0))))
,@(mapcar
(lambda (handler)
`((looking-at (,(phpinspect-handler-regexp-func-name handler)))
(setq token (,(phpinspect-handler-func-name handler) (match-string 0) max-point))
(when token
(phpinspect-pctx-register-token context token start-position (point)))))
handlers)
(t (forward-char)))
(when token
(setq tokens-rear (setcdr tokens-rear (cons token nil)))
(setq token nil))))
(when root
(phpinspect-pctx-register-token context tokens root-start (point)))
;; Return
tokens))))
(cl-defstruct (phpinspect-parser (:constructor phpinspect-make-parser))
(name 'root
:type symbol
:read-only t)
(tree-keyword "root"
:type string
:read-only t
:documentation "Name of the keyword that is used as car of the
root token, in string form without \":\" prefix.")
(handlers '(array tag equals list comma
attribute-reference variable
assignment-operator whitespace scope-keyword
static-keyword const-keyword use-keyword
class-keyword function-keyword word terminator
here-doc string comment block)
:type list
:read-only t
:documentation "A list of symbols referring to the
handlers that this parser uses.")
(delimiter-predicate nil
:type function
:read-only t
:documentation "A predicate function that is passed each
parsed token. When the predicate returns a non-nil value, the parser stops
executing.")
(func nil
:type 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."
(or (phpinspect-parser-func parser)
(setf (phpinspect-parser-func parser)
(phpinspect-make-parser-function
(phpinspect-parser-name parser)
(intern (concat ":" (phpinspect-parser-tree-keyword parser)))
(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)
(phpinspect-make-incremental-parser-function
(phpinspect-parser-name parser)
(intern (concat ":" (phpinspect-parser-tree-keyword parser)))
(phpinspect-parser-handlers parser)
(phpinspect-parser-delimiter-predicate parser)))))
(cl-defmethod phpinspect-parser-compile-entry ((parser phpinspect-parser))
(let ((func-name (phpinspect-parser-func-name (phpinspect-parser-name parser)))
(incremental-name (phpinspect-parser-func-name (phpinspect-parser-name parser) "incremental"))
(simple-name (phpinspect-parser-func-name (phpinspect-parser-name parser) "simple")))
`(defun ,func-name (buffer max-point &optional skip-over continue-condition root)
"Parse BUFFER, starting at point and ending at MAX-POINT.
If SKIP-OVER is non-nil, it must be a number of characters that
to skip over before starting to parse.
If CONTINUE-CONDITION is non-nil, it must be a function. It will
be called after each parsed child token with the token as
argument. If the return value is nil, parsing is stopped.
If ROOT is non-nil, this signals that there is no parent parser
that will take care of registering metadata for the parser's
returned token tree. So the parser should register the metadata
of the root of its returned tree itself, before
returning. Currently, token metadata is only registered when
parsing incrementally."
(if (and phpinspect-parse-context
(phpinspect-pctx-incremental phpinspect-parse-context))
(,incremental-name phpinspect-parse-context buffer max-point skip-over continue-condition root)
(,simple-name buffer max-point skip-over continue-condition root)))))
) ;; End of eval-when-compile
(defmacro phpinspect-defparser (name &rest parameters)
(declare (indent 1))
(unless (symbolp name)
(error "Name must be a symbol"))
(setq parameters (nconc parameters (list :name `(quote ,name))))
(let* ((func-name (phpinspect-parser-func-name name))
(simple-name (phpinspect-parser-func-name name "simple"))
(incremental-name (phpinspect-parser-func-name name "incremental")))
`(eval-when-compile
(let ((parser (phpinspect-make-parser ,@parameters)))
(defconst ,simple-name parser)
(defconst ,incremental-name parser)
(put (quote ,simple-name) 'phpinspect--parser t)
(put (quote ,incremental-name) 'phpinspect--incremental-parser t)
;; Stub function to please the byte compiler (real function will be
;; defined by `phpinspect-define-parser-functions'.
(defun ,func-name (_buffer _max-point &optional _skip-over _continue-condition _root))))))
(define-inline phpinspect-parser-func-bound-p (symbol)
(inline-quote (get ,symbol 'phpinspect--parser)))
(define-inline phpinspect-incremental-parser-func-bound-p (symbol)
(inline-quote (get ,symbol 'phpinspect--incremental-parser)))
(defun phpinspect-handler-bound-p (symbol)
(get symbol 'phpinspect--handler))
(defmacro phpinspect-define-parser-functions ()
(let (names incremental-names function-definitions)
(obarray-map (lambda (sym)
(cond ((phpinspect-parser-func-bound-p sym)
(push sym names))
((phpinspect-incremental-parser-func-bound-p sym)
(push sym incremental-names))))
obarray)
(dolist (name names)
(push (phpinspect-parser-compile-entry (symbol-value name))
function-definitions))
(dolist (name names)
(push (phpinspect-parser-compile (symbol-value name))
function-definitions))
(dolist (name incremental-names)
(push (phpinspect-parser-compile-incremental (symbol-value name))
function-definitions))
(push 'progn function-definitions)
function-definitions))
(phpinspect-defhandler comma (comma &rest _ignored)
"Handler for comma tokens"
((regexp . ","))
(phpinspect-munch-token-without-attribs comma :comma))
(phpinspect-defhandler word (word &rest --length)
"Handler for bareword tokens"
((regexp . "[A-Za-z_\\][\\A-Za-z_0-9]*"))
(setq --length (length word))
(forward-char --length)
(set-text-properties 0 --length nil word)
(list :word word))
(defmacro phpinspect-handler-regexp (handler-name)
(unless (symbolp handler-name)
(error "handler-name must be a known value and a symbol at compile time, name"))
(let ((name (phpinspect-handler-regexp-func-name handler-name)))
`(,name)))
(phpinspect-defhandler variable (start-token &rest _ignored)
"Handler for tokens indicating reference to a variable"
((regexp . "\\$"))
(forward-char (length start-token))
(if (looking-at (phpinspect-handler-regexp word))
(phpinspect-munch-token-without-attribs (match-string 0) :variable)
(list :variable nil)))
(phpinspect-defparser list
:tree-keyword "list"
:handlers '(array tag equals list comma
attribute-reference variable assignment-operator
whitespace function-keyword word terminator here-doc
string comment block-without-scopes))
(phpinspect-defhandler list (start-token max-point)
"Handler for php syntactic lists (Note: this does not include
datatypes like arrays, merely lists that are of a syntactic
nature like argument lists"
((regexp . "("))
(let* ((complete-list nil)
(php-list (phpinspect--parse-list
(current-buffer)
max-point
(length start-token)
(lambda () (not (and (char-equal (char-after) ?\)) (setq complete-list t)))))))
(if complete-list
;; Prevent parent-lists (if any) from exiting by skipping over the
;; ")" character
(forward-char)
(setcar php-list :incomplete-list))
php-list))
(defsubst phpinspect--parse-annotation-parameters (parameter-amount)
(let* ((words)
(list-regexp (phpinspect-handler-regexp list))
;; Return annotations may end with "[]" for collections.
(word-regexp (concat (phpinspect-handler-regexp word) "\\(\\[\\]\\)?"))
(variable-regexp (phpinspect-handler-regexp variable))
(annotation-regexp (phpinspect-handler-regexp annotation)))
(while (not (or (looking-at annotation-regexp)
(= (point) (point-max))
(= (length words) parameter-amount)))
(cond ((looking-at list-regexp)
(push (phpinspect--list-handler (match-string 0) (point-max)) words))
((looking-at word-regexp)
(push (phpinspect--word-handler (match-string 0)) words))
((looking-at variable-regexp)
(push (phpinspect--variable-handler (match-string 0)) words))
(t (forward-char))))
(nreverse words)))
(phpinspect-defhandler annotation (start-token &rest _ignored)
"Handler for in-comment @annotations"
((regexp . "@"))
(forward-char (length start-token))
(if (looking-at (phpinspect-handler-regexp word))
(let ((annotation-name (match-string 0)))
(forward-char (length annotation-name))
(cond ((string= annotation-name "var")
;; The @var annotation accepts 2 parameters:
;; the type and the $variable name
(cons :var-annotation
(phpinspect--parse-annotation-parameters 2)))
((string= annotation-name "return")
;; The @return annotation only accepts 1 word as parameter:
;; The return type
(cons :return-annotation
(phpinspect--parse-annotation-parameters 1)))
((string= annotation-name "param")
;; The @param annotation accepts 2 parameters:
;; The type of the param, and the param's $name
(cons :param-annotation
(phpinspect--parse-annotation-parameters 2)))
((string= annotation-name "method")
(cons :method-annotation
(phpinspect--parse-annotation-parameters 3)))
(t
(list :annotation annotation-name))))
(list :annotation nil)))
(phpinspect-defhandler tag (start-token max-point)
"Handler that discards any inline HTML it encounters"
((regexp . "\\?>"))
(forward-char (length start-token))
(or (re-search-forward "<\\?php\\|<\\?" nil t)
(goto-char max-point))
(list :html))
(phpinspect-defparser doc-block
:tree-keyword "doc-block"
:handlers '(annotation whitespace))
(phpinspect-defparser comment
:tree-keyword "comment"
:handlers '(tag)
:delimiter-predicate #'phpinspect-html-p)
(phpinspect-defhandler comment (start-token max-point)
"Handler for comments and doc blocks"
((regexp . "#\\|//\\|/\\*"))
(forward-char (length start-token))
(cond ((string-match "/\\*" start-token)
(let* ((region-start (point))
;; Move to the end of the comment region
(region-end
(progn
(while (not (or (= max-point (point)) (looking-at "\\*/")))
(forward-char))
(point)))
(doc-block (save-restriction
(goto-char region-start)
(narrow-to-region region-start region-end)
(phpinspect--parse-doc-block (current-buffer) (point-max)))))
(forward-char 2)
doc-block))
(t
(let ((end-position (line-end-position)))
(phpinspect--parse-comment (current-buffer) end-position)))))
(phpinspect-defhandler class-variable (start-token &rest _ignored)
"Handler for tokens indicating reference to a variable"
((regexp . "\\$"))
(forward-char (length start-token))
(if (looking-at (phpinspect-handler-regexp word))
(phpinspect-munch-token-without-attribs (match-string 0) :class-variable)
(list :class-variable nil)))
(phpinspect-defhandler whitespace (whitespace &rest _ignored)
"Handler that discards whitespace"
((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)
"Handler for strict and unstrict equality comparison tokens."
((regexp . "===?"))
(phpinspect-munch-token-without-attribs equals :equals))
(phpinspect-defhandler assignment-operator (operator &rest _ignored)
"Handler for tokens indicating that an assignment is taking place"
((regexp . "[+-]?="))
(phpinspect-munch-token-without-attribs operator :assignment))
(phpinspect-defhandler terminator (terminator &rest _ignored)
"Handler for statement terminators."
((regexp . ";"))
(phpinspect-munch-token-without-attribs terminator :terminator))
(phpinspect-defparser use
:tree-keyword "use"
:handlers '(word tag block-without-scopes terminator)
:delimiter-predicate #'phpinspect-end-of-use-p)
(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-word-end-space start-token))
(forward-char (length start-token))
(phpinspect--parse-use (current-buffer) max-point))
(phpinspect-defhandler attribute-reference (start-token &rest _ignored)
"Handler for references to object attributes, or static class attributes."
((regexp . "->\\|::"))
(forward-char (length start-token))
(looking-at (phpinspect-handler-regexp word))
(let ((name (if (looking-at (phpinspect-handler-regexp word))
(phpinspect--word-handler (match-string 0))
nil)))
(cond
((string= start-token "::")
(list :static-attrib name))
((string= start-token "->")
(list :object-attrib name)))))
(phpinspect-defparser namespace
:tree-keyword "namespace"
:delimiter-predicate #'phpinspect-block-p)
(phpinspect-defhandler namespace (start-token max-point)
"Handler for the namespace keyword. This is a special one
because it is not always delimited by a block like classes or
functions. This handler parses the namespace declaration, and
then continues to parse subsequent tokens, only stopping when
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-word-end-space start-token))
(forward-char (length start-token))
(phpinspect--parse-namespace
(current-buffer)
max-point
nil
(lambda () (not (looking-at (phpinspect-handler-regexp namespace))))))
(phpinspect-defparser const
:tree-keyword "const"
:handlers '(word comment assignment-operator string array terminator)
:delimiter-predicate #'phpinspect-end-of-token-p)
(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-word-end-space start-token))
(forward-char (length start-token))
(setq start-token (phpinspect--parse-const (current-buffer) max-point))
(when (phpinspect-incomplete-token-p (car (last start-token)))
(setcar start-token :incomplete-const))
start-token)
(phpinspect-defhandler string (start-token &rest _ignored)
"Handler for strings"
((regexp . "\\(\"\\|'\\)"))
(list :string (phpinspect--munch-string start-token)))
(phpinspect-defparser block-without-scopes
:tree-keyword "block"
:handlers '(array tag equals list comma attribute-reference variable
assignment-operator whitespace function-keyword word
terminator here-doc string comment block-without-scopes))
(phpinspect-defhandler block-without-scopes (start-token max-point)
"Handler for code blocks that cannot contain scope, const or
static keywords with the same meaning as in a class block."
((regexp . "{"))
(let* ((complete-block nil)
(continue-condition (lambda ()
(not (and (char-equal (char-after) ?})
(setq complete-block t)))))
(parsed (phpinspect--parse-block-without-scopes
(current-buffer) max-point (length start-token) continue-condition 'root)))
(if complete-block
(forward-char)
(setcar parsed :incomplete-block))
parsed))
(phpinspect-defparser class-block
:tree-keyword "block"
:handlers '(array tag equals list comma attribute-reference class-variable
assignment-operator whitespace scope-keyword static-keyword
const-keyword use-keyword function-keyword word terminator
here-doc string comment block))
(phpinspect-defhandler class-block (start-token max-point)
"Handler for code blocks that cannot contain classes"
((regexp . "{"))
(forward-char (length start-token))
(let* ((complete-block nil)
(continue-condition (lambda ()
(not (and (char-equal (char-after) ?})
(setq complete-block t)))))
(parsed (phpinspect--parse-class-block
(current-buffer) max-point (length start-token) continue-condition 'root)))
(if complete-block
(forward-char)
(setcar parsed :incomplete-block))
parsed))
(phpinspect-defparser block
:tree-keyword "block")
(phpinspect-defhandler block (start-token max-point)
"Handler for code blocks"
((regexp . "{"))
(let* ((complete-block nil)
(continue-condition (lambda ()
;; When we encounter a closing brace for this
;; block, we can mark the block as complete.
(not (and (char-equal (char-after) ?})
(setq complete-block t)))))
(parsed (phpinspect--parse-block
(current-buffer) max-point (length start-token) continue-condition)))
(if complete-block
;; After meeting the char-after requirement above, we need to move
;; one char forward to prevent parent-blocks from exiting because
;; of the same char.
(forward-char)
(setcar parsed :incomplete-block))
parsed))
(phpinspect-defhandler here-doc (start-token &rest _ignored)
"Handler for heredocs. Discards their contents."
((regexp . "<<<"))
(forward-char (length start-token))
(if (looking-at "[A-Za-z0-9'\"\\_]+")
(re-search-forward (concat "^" (regexp-quote (match-string 0))) nil t))
(list :here-doc))
(define-inline phpinspect--munch-string (start-token)
"Consume text at point until a non-escaped `START-TOKEN` is found.
Returns the consumed text string without face properties."
(inline-letevals (start-token)
(inline-quote
(progn
(forward-char (length ,start-token))
(let ((start-point (point)))
(cond ((looking-at ,start-token)
(forward-char)
"")
((looking-at (concat "\\([\\][\\]\\)+" (regexp-quote ,start-token)))
(let ((match (match-string 0)))
(forward-char (length match))
(buffer-substring-no-properties start-point
(+ start-point (- (length match)
(length ,start-token))))))
(t
(re-search-forward (format "\\([^\\]\\([\\][\\]\\)+\\|[^\\]\\)%s"
(regexp-quote ,start-token))
nil t)
(buffer-substring-no-properties start-point (- (point) 1)))))))))
(phpinspect-defparser declaration
:tree-keyword "declaration"
:handlers '(comment word list terminator tag)
:delimiter-predicate #'phpinspect-end-of-token-p)
;; TODO: Look into using different names for function and class :declaration tokens. They
;; don't necessarily require the same handlers to parse.
(define-inline phpinspect-parse-declaration (buffer max-point &optional continue-condition root)
(inline-quote
(let ((result (phpinspect--parse-declaration ,buffer ,max-point nil ,continue-condition ,root)))
(if (phpinspect-terminator-p (car (last result)))
(butlast result)
result))))
(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-word-end-space start-token))
(let* ((continue-condition (lambda () (not (or (char-equal (char-after) ?{)
(char-equal (char-after) ?})))))
(declaration (phpinspect-parse-declaration (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
(phpinspect--block-without-scopes-handler
(char-to-string (char-after)) max-point)))))
(phpinspect-defparser scope-public
:tree-keyword "public"
:handlers '(function-keyword static-keyword const-keyword class-variable here-doc
string terminator tag comment)
:delimiter-predicate #'phpinspect--scope-terminator-p)
(phpinspect-defparser scope-private
:tree-keyword "private"
:handlers '(function-keyword static-keyword const-keyword class-variable here-doc
string terminator tag comment)
:delimiter-predicate #'phpinspect--scope-terminator-p)
(phpinspect-defparser scope-protected
:tree-keyword "protected"
:handlers '(function-keyword static-keyword const-keyword class-variable here-doc
string terminator tag comment)
:delimiter-predicate #'phpinspect--scope-terminator-p)
(phpinspect-defhandler scope-keyword (start-token max-point)
"Handler for scope keywords"
((regexp . (mapconcat (lambda (word)
(concat word (phpinspect--word-end-regex)))
(list "public" "private" "protected")
"\\|")))
(setq start-token (phpinspect--strip-word-end-space start-token))
(forward-char (length start-token))
(cond ((string= start-token "public") (phpinspect--parse-scope-public (current-buffer) max-point))
((string= start-token "private") (phpinspect--parse-scope-private (current-buffer) max-point))
((string= start-token "protected") (phpinspect--parse-scope-protected (current-buffer) max-point))))
(phpinspect-defparser static
:tree-keyword "static"
:handlers '(comment function-keyword class-variable array word terminator tag)
:delimiter-predicate #'phpinspect--static-terminator-p)
(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-word-end-space start-token))
(forward-char (length start-token))
(phpinspect--parse-static (current-buffer) max-point))
(phpinspect-defhandler fat-arrow (arrow &rest _ignored)
"Handler for the \"fat arrow\" in arrays and foreach expressions"
((regexp . "=>"))
(phpinspect-munch-token-without-attribs arrow :fat-arrow))
(phpinspect-defparser array
:tree-keyword "array"
:handlers '(comment comma list here-doc string array variable
attribute-reference word fat-arrow))
(phpinspect-defhandler array (start-token max-point)
"Handler for arrays, in the bracketet as well as the list notation"
((regexp . "\\[\\|array("))
(let* ((end-char (cond ((string= start-token "[") ?\])
((string= start-token "array(") ?\))))
(end-char-reached nil)
(token (phpinspect--parse-array
(current-buffer)
max-point
(length start-token)
(lambda () (not (and (char-equal (char-after) end-char)
(setq end-char-reached t)))))))
;; Skip over the end char to prevent enclosing arrays or lists
;; from terminating.
(if end-char-reached
(forward-char)
;; Signal incompleteness when terminated because of max-point
(setcar token :incomplete-array))
token))
(phpinspect-defhandler class-keyword (start-token max-point)
"Handler for the class keyword, and tokens that follow to define
the properties of the class"
((regexp . (concat "\\(abstract\\|final\\|class\\|interface\\|trait\\)"
(phpinspect--word-end-regex))))
(setq start-token (phpinspect--strip-word-end-space start-token))
`(:class ,(phpinspect-parse-declaration
(current-buffer)
max-point
(lambda () (not (char-equal (char-after) ?{)))
'root)
,@(when (looking-at (phpinspect--class-block-handler-regexp))
(list (phpinspect--class-block-handler
(char-to-string (char-after)) max-point)))))
(phpinspect-defparser root
:tree-keyword "root"
:handlers '(namespace array equals list comma attribute-reference variable
assignment-operator whitespace scope-keyword
static-keyword const-keyword use-keyword class-keyword
function-keyword word terminator here-doc string comment
tag block))
(defun phpinspect-parse-current-buffer ()
(phpinspect-parse-buffer-until-point
(current-buffer)
(point-max)))
(defun phpinspect-parse-string (string)
(with-temp-buffer
(insert string)
(phpinspect-parse-current-buffer)))
(defun phpinspect-parse-file (file)
(with-temp-buffer
(insert-file-contents file)
(phpinspect-parse-current-buffer)))
;; Define all registered parser functions
(eval-and-compile
(phpinspect-define-parser-functions))
(defun phpinspect-parse-buffer-until-point (buffer point)
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(re-search-forward "<\\?php\\|<\\?" nil t)
(phpinspect--parse-root (current-buffer) point nil nil 'root))))
(provide 'phpinspect-parser)
;;; phpinspect-parser.el ends here

@ -1,394 +0,0 @@
;;; phpinspect-pipeline.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-queue)
(require 'phpinspect-util)
(define-error 'phpinspect-pipeline-incoming "Signal for incoming pipeline data")
(define-error 'phpinspect-pipeline-error "Signal for pipeline errors")
(defcustom phpinspect-pipeline-pause-time 0.5
"Number of seconds to pause a pipeline thread when emacs receives
user input. This is similar to `phpinspect-worker-pause-time',
but pipelines are meant to run in bursts. For that reason, the
default pause time for pipelines is lower to be a little more
aggressive in hogging cpu time.
Set this variable to a higher value if you experience a lot of
jitter when editing during pipeline operations. At the time of
writing, pipelines are used to refresh the project
index/autoloader and for the indexation of \"include\"
directories."
:type 'number
:group 'phpinspect)
(cl-defstruct (phpinspect-pipeline-end (:constructor phpinspect-make-pipeline-end))
(value nil
:type any)
(error nil)
(thread nil
:type thread))
(cl-defstruct (phpinspect-pipeline-emission (:constructor phpinspect-make-pipeline-emission))
(collection nil
:type list))
(cl-defstruct (phpinspect-pipeline-thread (:constructor phpinspect-make-pipeline-thread))
(in-queue nil
:type phpinspect-queue)
(end nil
:type boolean))
(cl-defstruct (phpinspect-pipeline-ctx (:constructor phpinspect-make-pipeline-ctx))
(threads nil
:type alist))
(cl-defmethod phpinspect-pipeline-ctx-register-thread ((ctx phpinspect-pipeline-ctx) thread in-queue)
(push (cons thread (phpinspect-make-pipeline-thread :in-queue in-queue))
(phpinspect-pipeline-ctx-threads ctx)))
(cl-defmethod phpinspect-pipeline-ctx-get-thread ((ctx phpinspect-pipeline-ctx) thread)
(alist-get thread (phpinspect-pipeline-ctx-threads ctx)
nil nil #'eq))
(cl-defmethod phpinspect-pipeline-ctx-register-end ((ctx phpinspect-pipeline-ctx) (end phpinspect-pipeline-end))
(let ((thread (phpinspect-pipeline-ctx-get-thread ctx (phpinspect-pipeline-end-thread end))))
(setf (phpinspect-pipeline-thread-end thread) end)))
(cl-defmethod phpinspect-pipeline-ctx-close ((ctx phpinspect-pipeline-ctx))
(let (errors err end thread-live)
(dolist (thread (phpinspect-pipeline-ctx-threads ctx))
(setq end (phpinspect-pipeline-thread-end (cdr thread))
err (or (thread-last-error (car thread))
(and end (phpinspect-pipeline-end-error end)))
thread-live (thread-live-p (car thread)))
(when thread-live
(if end
(setq errors (nconc errors (list (format "Thread %s ended pipeline, but is still running"
(thread-name (car thread))))))
(setq errors (nconc errors (list (format "Thread %s is still running when pipeline is closing"
(thread-name (car thread))))))))
(when err
(setq errors (nconc errors (list (format "Thread %s signaled error: %s"
(thread-name (car thread))
err)))))
(unless end
(setq errors (nconc errors (list (format "Thread %s never ended"
(thread-name (car thread)))))))
(when (thread-live-p (car thread))
(thread-signal (car thread) 'quit nil)))
(when errors
(signal 'phpinspect-pipeline-error errors))))
(define-inline phpinspect-pipeline-emit (data)
(inline-letevals (data)
(inline-quote
(throw 'phpinspect-pipeline-emit ,data))))
(define-inline phpinspect-pipeline-emit-all (collection)
(inline-letevals (collection)
(inline-quote
(throw 'phpinspect-pipeline-emit
(if ,collection
(phpinspect-make-pipeline-emission
:collection ,collection)
,collection)))))
(defmacro phpinspect-pipeline-end (&optional value)
(if value
`(throw 'phpinspect-pipeline-emit
(phpinspect-make-pipeline-end :value ,value :thread (current-thread)))
`(throw 'phpinspect-pipeline-emit
(phpinspect-make-pipeline-end :thread (current-thread)))))
(define-inline phpinspect-pipeline-pause ()
"Pause the current pipeline thread"
(inline-quote
(if (phpinspect--input-pending-p)
(let ((mx (make-mutex)))
(phpinspect-thread-pause
phpinspect-pipeline-pause-time mx (make-condition-variable mx "phpinspect-pipeline-pause")))
(thread-yield))))
(define-inline phpinspect--read-pipeline-emission (&rest body)
(push 'progn body)
(inline-quote
(catch 'phpinspect-pipeline-emit
,body
nil)))
(defmacro phpinspect--run-as-pipeline-step (func-name queue consumer-queue pipeline-ctx &optional local-ctx)
(unless (symbolp func-name)
(error "Function name must be a symbol, got: %s" func-name))
(let* ((thread-name (concat "phpinspect-pipeline-" (symbol-name func-name)))
(statement (list func-name))
(statement-rear statement)
(incoming (gensym "incoming"))
(outgoing (gensym "outgoing"))
(inc-queue (gensym "queue"))
(out-queue (gensym "queue"))
(context-sym (gensym "context"))
(continue-running (gensym "continue-running"))
(pctx-sym (gensym "pipeline-ctx"))
(incoming-end (gensym "incoming-end"))
(end (gensym "end")))
(when local-ctx
(setq statement-rear (setcdr statement-rear (cons context-sym nil))))
(setq statement-rear (setcdr statement-rear (cons incoming nil)))
`(let ((,inc-queue ,queue)
(,out-queue ,consumer-queue)
(,context-sym ,local-ctx)
(,pctx-sym ,pipeline-ctx))
(make-thread
(lambda ()
(let ((,continue-running t)
,incoming ,outgoing ,end ,incoming-end)
(phpinspect-pipeline--register-wakeup-function ,inc-queue)
(while ,continue-running
(condition-case-unless-debug err
(progn
(phpinspect-pipeline-pause)
;; Prevent quitting during step execution, as this could
;; break data integrity.
(let ((inhibit-quit t))
(setq ,incoming (phpinspect-pipeline-receive ,inc-queue))
(if (phpinspect-pipeline-end-p ,incoming)
(progn
(setq ,incoming-end ,incoming)
(when (phpinspect-pipeline-end-value ,incoming)
(progn
(setq ,incoming (phpinspect-pipeline-end-value ,incoming)
,outgoing (phpinspect--read-pipeline-emission ,statement))
(phpinspect-pipeline--enqueue ,out-queue ,outgoing 'no-notify)))
(setq ,end (phpinspect-make-pipeline-end :thread (current-thread)))
(phpinspect-pipeline-ctx-register-end ,pctx-sym ,end)
(setq ,continue-running nil)
(phpinspect-pipeline--enqueue ,out-queue ,end))
;; Else
(setq ,outgoing (phpinspect--read-pipeline-emission ,statement))
(when (phpinspect-pipeline-end-p ,outgoing)
(setq ,end (phpinspect-make-pipeline-end :thread (current-thread)))
(phpinspect-pipeline-ctx-register-end ,pctx-sym ,end)
(setq ,continue-running nil))
(phpinspect-pipeline--enqueue ,out-queue ,outgoing))))
(quit (ignore-error phpinspect-pipeline-incoming
(phpinspect-pipeline-pause)))
(phpinspect-pipeline-incoming)
(t (phpinspect--log "Pipeline thread errored: %s" err)
(setq ,end (phpinspect-make-pipeline-end :thread (current-thread) :error err))
(setq ,continue-running nil)
(phpinspect-pipeline-ctx-register-end ,pctx-sym ,end)
(phpinspect-pipeline--enqueue ,out-queue ,end))))))
,thread-name))))
(defun phpinspect--chain-pipeline-steps (steps start-queue end-queue ctx)
(let ((result (gensym "result"))
(incoming (gensym "incoming"))
(outgoing (gensym "outgoing"))
(ctx-sym (gensym "ctx"))
body name step statement)
(while (setq step (pop steps))
(setq name (phpinspect--pipeline-step-name step))
(setq statement
(if (phpinspect--pipeline-step--context-var-name step)
`(phpinspect--run-as-pipeline-step
,name ,incoming ,outgoing ,ctx-sym ,(phpinspect--pipeline-step--context-var-name step))
`(phpinspect--run-as-pipeline-step ,name ,incoming ,outgoing ,ctx-sym)))
(setq body (nconc body `(,(if steps
`(setq ,outgoing (phpinspect-make-queue))
`(setq ,outgoing ,end-queue))
(phpinspect-pipeline-ctx-register-thread ,ctx-sym ,statement ,incoming)
(setq ,incoming ,outgoing)))))
`(let ((,incoming ,start-queue) (,ctx-sym ,ctx) ,result ,outgoing)
,@body)))
(cl-defstruct (phpinspect--pipeline-step (:constructor phpinspect--make-pipeline-step))
(context nil
:type any
:documentation
"An object that is passed as first argument to all step executions")
(-context-var-name nil
:type symbol
:documentation
"Variable name used to store context in")
(name nil
:type symbol
:documentation
"The name of this step"))
(defmacro phpinspect--pipeline (seed-form &rest parameters)
(let (key value steps let-vars)
(while parameters
(setq key (pop parameters)
value (pop parameters))
(pcase key
(:into
(let* ((construct-params (cons nil nil))
(cons-params-rear construct-params)
parameters name)
(if (listp value)
(progn
(setq name (car value)
parameters (cdr value)))
(setq name value))
(unless (symbolp name)
(error "Step name should be a symbol"))
(let (key value)
(while parameters
(setq key (pop parameters)
value (pop parameters))
(setq key (intern (string-replace ":with-" ":" (symbol-name key))))
(setq cons-params-rear
(setcdr cons-params-rear (cons key (cons value nil))))))
(push (apply #'phpinspect--make-pipeline-step `(,@(cdr construct-params) :name ,name))
steps)))
(_ (error "unexpected key %s" key))))
(setq steps (nreverse steps))
(dolist (step steps)
(when (phpinspect--pipeline-step-context step)
(setf (phpinspect--pipeline-step--context-var-name step) (gensym "ctx"))
(push `(,(phpinspect--pipeline-step--context-var-name step)
,(phpinspect--pipeline-step-context step))
let-vars)))
(let ((queue-sym (gensym "queue"))
(end-queue-sym (gensym "end-queue"))
(ctx-sym (gensym "ctx"))
(recv-sym (gensym))
(result-sym (gensym))
(seed-sym (gensym))
(collecting-sym (gensym)))
`(progn
(when (eq main-thread (current-thread))
(error "Pipelines should not run in the main thread"))
(let* (,@let-vars
(,ctx-sym (phpinspect-make-pipeline-ctx))
(,queue-sym (phpinspect-make-queue))
(,end-queue-sym (phpinspect-make-queue))
(,collecting-sym t)
,recv-sym ,result-sym ,seed-sym)
,(phpinspect--chain-pipeline-steps steps queue-sym end-queue-sym ctx-sym)
(setq ,seed-sym ,seed-form)
(when ,seed-sym
(phpinspect-pipeline--enqueue
,queue-sym
(phpinspect-make-pipeline-emission :collection ,seed-form) 'no-notify))
(phpinspect-pipeline--enqueue
,queue-sym (phpinspect-make-pipeline-end :thread (current-thread)))
(while ,collecting-sym
(ignore-error phpinspect-pipeline-incoming
(progn
(phpinspect-pipeline--register-wakeup-function ,end-queue-sym)
(while (not (phpinspect-pipeline-end-p
(setq ,recv-sym (phpinspect-pipeline-receive ,end-queue-sym))))
(setq ,result-sym (nconc ,result-sym (list ,recv-sym))))
(setq ,collecting-sym nil))))
(phpinspect-pipeline-ctx-close ,ctx-sym)
,result-sym)))))
(defmacro phpinspect-pipeline (seed-form &rest parameters)
(declare (indent defun))
(let ((result (gensym))
(async-sym (gensym))
key value async macro-params)
(while parameters
(setq key (pop parameters)
value (pop parameters))
(pcase key
(:async (setq async value))
(_ (setq macro-params (nconc macro-params (list key value))))))
`(if-let ((,async-sym ,async))
(make-thread
(lambda ()
(condition-case err
(let ((,result (phpinspect--pipeline ,seed-form ,@macro-params)))
(funcall ,async-sym (or ,result 'phpinspect-pipeline-nil-result) nil))
(t (funcall ,async-sym nil err))))
"phpinspect-pipeline-async")
(phpinspect--pipeline ,seed-form ,@macro-params))))
(define-inline phpinspect-pipeline-receive (queue)
(inline-letevals (queue)
(inline-quote
(let ((val))
(while (not (setq val (phpinspect-queue-dequeue ,queue)))
(thread-yield))
val))))
(defun phpinspect-pipeline-step-name (name &optional suffix)
(intern (concat (symbol-name name) (if suffix (concat "-" suffix) ""))))
(define-inline phpinspect-pipeline--register-wakeup-function (queue)
(inline-quote
(let ((thread (current-thread)))
(setf (phpinspect-queue-subscription ,queue)
(lambda () (thread-signal thread 'phpinspect-pipeline-incoming nil))))))
(define-inline phpinspect-pipeline--enqueue (queue emission &optional no-notify)
(inline-letevals (queue emission no-notify)
(inline-quote
(when ,emission
(if (phpinspect-pipeline-emission-p ,emission)
(when (phpinspect-pipeline-emission-collection ,emission)
(while (cdr (phpinspect-pipeline-emission-collection ,emission))
(phpinspect-queue-enqueue
,queue (pop (phpinspect-pipeline-emission-collection ,emission))
,no-notify))
(phpinspect-queue-enqueue
,queue (pop (phpinspect-pipeline-emission-collection ,emission)) ,no-notify))
(phpinspect-queue-enqueue ,queue ,emission ,no-notify))))))
(provide 'phpinspect-pipeline)
;;; phpinspect-pipeline.el ends here

@ -1,95 +0,0 @@
;;; phpinspect-project-struct.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
(eval-when-compile
(declare-function phpinspect-make-dynamic-worker "phpinspect-worker.el"))
(cl-defstruct (phpinspect-project (:constructor phpinspect--make-project))
(read-only-p nil
:type boolean
:documentation
"Whether this project instance is read-only, meaning that its data
should never be changed.
When this slot has a non-nil value:
- Methods and functions that are meant to manipulate class data
should become no-ops.
- All classes retrieved from it should be marked as read-only as well.")
(extra-class-retriever nil
:type lambda
:documentation
"A function that should accept a `phpinspect--type' and return
matching `phpinspect--class' instances or nil. Used to discover
classes that are defined outside of project code.")
(extra-function-retriever nil
:type lambda
:documentation
"A function that should accept a `phpinspect-name' (see
`phpinspect-intern-name') and return matching `phpinspect--function'
instances or nil. Used to discover functions that are defined
outside of project code.")
(class-index (make-hash-table :test 'eq :size 100 :rehash-size 1.5)
:type hash-table
:documentation
"A `hash-table` that contains all of the currently
indexed classes in the project")
(function-index (make-hash-table :test 'eq :size 100 :rehash-size 2.0)
:type hash-table
:documentation
"A hash able that contains all of the currently indexed functions
in the project")
(function-token-index (make-hash-table :test 'eq :size 100 :rehash-size 1.5))
(fs nil
:type phpinspect-fs
:documentation
"The filesystem object through which this project's files
can be accessed.")
(autoload nil
:type phpinspect-autoload
:documentation
"The autoload object through which this project's type
definitions can be retrieved")
(worker (progn
(unless (featurep 'phpinspect-worker)
(require 'phpinspect-worker))
(phpinspect-make-dynamic-worker))
:type phpinspect-worker
:documentation
"The worker that this project may queue tasks for")
(root nil
:type string
:documentation
"The root directory of this project")
(purged nil
:type boolean
:documentation "Whether or not the project has been purged or not.
Projects get purged when they are removed from the global cache.")
(file-watchers (make-hash-table :test #'equal :size 10000 :rehash-size 10000)
:type hash-table
:documentation "All active file watchers in this project,
indexed by the absolute paths of the files they're watching."))
(provide 'phpinspect-project-struct)

@ -1,404 +0,0 @@
;;; phpinspect-project.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-project-struct)
(require 'phpinspect-autoload)
(require 'phpinspect-worker)
(require 'phpinspect-index)
(require 'phpinspect-class)
(require 'phpinspect-type)
(require 'phpinspect-fs)
(require 'filenotify)
(defvar phpinspect-auto-reindex nil
"Whether or not phpinspect should automatically search for new
files. The current implementation is clumsy and can result in
serious performance hits. Enable at your own risk (:")
(defvar phpinspect-project-root-function #'phpinspect--find-project-root
"Function that phpinspect uses to find the root directory of a project.")
(defvar-local phpinspect--buffer-project nil
"The root directory of the PHP project that this buffer belongs to")
(defmacro phpinspect-project-edit (project &rest body)
(declare (indent 1))
`(unless (phpinspect-project-read-only-p ,project)
,@body))
(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)
(cl-defmethod phpinspect-project-purge ((project phpinspect-project))
"Disable all background processes for project and put it in a `purged` state."
(maphash (lambda (_ watcher) (file-notify-rm-watch watcher))
(phpinspect-project-file-watchers project))
(setf (phpinspect-project-file-watchers project)
(make-hash-table :test #'equal :size 10000 :rehash-size 10000))
(setf (phpinspect-project-purged project) t))
(cl-defmethod phpinspect-project-watch-file ((project phpinspect-project)
filepath
callback)
(phpinspect-project-edit project
(let ((watcher (file-notify-add-watch filepath '(change) callback)))
(puthash filepath watcher (phpinspect-project-file-watchers project)))))
(cl-defmethod phpinspect-project-add-return-types-to-index-queueue
((project phpinspect-project) methods)
(phpinspect-project-edit project
(dolist (method methods)
(when (phpinspect--function-return-type method)
(phpinspect-project-enqueue-if-not-present
project
(phpinspect--function-return-type method))))))
(cl-defmethod phpinspect-project-add-variable-types-to-index-queue
((project phpinspect-project) variables)
(phpinspect-project-edit project
(dolist (var variables)
(when (phpinspect--variable-type var)
(phpinspect-project-enqueue-if-not-present project (phpinspect--variable-type var))))))
(cl-defmethod phpinspect-project-enqueue-if-not-present
((project phpinspect-project) (type phpinspect--type))
(phpinspect-project-edit project
(unless (phpinspect--type-is-native type)
(let ((class (phpinspect-project-get-class project type)))
(when (or (not class)
(not (or (phpinspect--class-initial-index class))))
(when (not class)
(setq class (phpinspect-project-create-class project type)))
(unless (or (phpinspect--type= phpinspect--null-type type)
(phpinspect--type-is-native type))
(phpinspect--log "Adding unpresent class %s to index queue" type)
(phpinspect-worker-enqueue (phpinspect-project-worker project)
(phpinspect-make-index-task project type))))))))
(cl-defmethod phpinspect-project-add-class-attribute-types-to-index-queue
((project phpinspect-project) (class phpinspect--class))
(phpinspect-project-edit project
(phpinspect-project-add-return-types-to-index-queueue
project
(phpinspect--class-get-method-list class))
(phpinspect-project-add-return-types-to-index-queueue
project
(phpinspect--class-get-static-method-list class))
(phpinspect-project-add-variable-types-to-index-queue
project
(phpinspect--class-variables class))))
(cl-defmethod phpinspect-project-add-index
((project phpinspect-project) (index (head phpinspect--root-index)) &optional index-imports)
(phpinspect-project-edit project
(when index-imports
(phpinspect-project-enqueue-imports project (alist-get 'imports (cdr index))))
(dolist (indexed-class (alist-get 'classes (cdr index)))
(phpinspect-project-add-class project (cdr indexed-class) index-imports))
(dolist (func (alist-get 'functions (cdr index)))
(phpinspect-project-set-function project func))))
(cl-defmethod phpinspect-project-set-function
((project phpinspect-project) (func phpinspect--function))
(phpinspect-project-edit project
(puthash (phpinspect--function-name-symbol func) func
(phpinspect-project-function-index project))))
(cl-defmethod phpinspect-project-get-function
((project phpinspect-project) (name (head phpinspect-name)))
(gethash name (phpinspect-project-function-index project)))
(cl-defmethod phpinspect-project-get-function-or-extra
((project phpinspect-project) (name (head phpinspect-name)))
(or (phpinspect-project-get-function project name)
(and (phpinspect-project-extra-function-retriever project)
(funcall (phpinspect-project-extra-function-retriever project)
name))))
(cl-defmethod phpinspect-project-delete-function
((project phpinspect-project) (name (head phpinspect-name)))
(phpinspect-project-edit project
(remhash name (phpinspect-project-function-index project))))
(cl-defmethod phpinspect-project-get-functions ((project phpinspect-project))
(let ((funcs))
(maphash
(lambda (_name func) (push func funcs))
(phpinspect-project-function-index project))
funcs))
(cl-defmethod phpinspect-project-get-functions-with-extra ((project phpinspect-project))
(let ((funcs))
(maphash
(lambda (_name func) (push func funcs))
(phpinspect-project-function-index project))
(if (phpinspect-project-extra-function-retriever project)
(nconc funcs (funcall (phpinspect-project-extra-function-retriever project) nil))
funcs)))
(cl-defmethod phpinspect-project-enqueue-imports
((project phpinspect-project) imports)
(phpinspect-project-edit project
(dolist (import imports)
(when import
(phpinspect--log "Adding import to index queue: %s" import)
(phpinspect-project-enqueue-if-not-present project (cdr import))))))
(cl-defmethod phpinspect-project-delete-class ((project phpinspect-project) (class phpinspect--class))
(phpinspect-project-delete-class project (phpinspect--class-name class)))
(cl-defmethod phpinspect-project-delete-class ((project phpinspect-project) (class-name phpinspect--type))
(phpinspect-project-edit project
(remhash (phpinspect--type-name-symbol class-name) (phpinspect-project-class-index project))))
(cl-defmethod phpinspect-project-add-class
((project phpinspect-project) (indexed-class (head phpinspect--indexed-class)) &optional index-imports)
(phpinspect-project-edit project
(if (not (alist-get 'class-name (cdr indexed-class)))
(phpinspect--log "Error: Class with declaration %s does not have a name" (alist-get 'declaration indexed-class))
;; Else
(let* ((class-name (phpinspect--type-name-symbol
(alist-get 'class-name (cdr indexed-class))))
(class (gethash class-name
(phpinspect-project-class-index project))))
(unless class
(setq class (phpinspect--make-class-generated
:class-retriever (phpinspect-project-make-class-retriever project))))
(when index-imports
(phpinspect-project-enqueue-imports
project (alist-get 'imports (cdr indexed-class))))
(phpinspect--class-set-index class indexed-class)
(puthash class-name class (phpinspect-project-class-index project))
(phpinspect-project-add-class-attribute-types-to-index-queue project class)))))
(cl-defmethod phpinspect-project-set-class
((project phpinspect-project) (class-fqn phpinspect--type) (class phpinspect--class))
(phpinspect-project-edit project
(puthash (phpinspect--type-name-symbol class-fqn)
class
(phpinspect-project-class-index project))))
(cl-defmethod phpinspect-project-create-class
((project phpinspect-project) (class-fqn phpinspect--type))
(phpinspect-project-edit project
(let ((class (phpinspect--make-class-generated
:class-retriever (phpinspect-project-make-class-retriever project))))
(phpinspect-project-set-class project class-fqn class)
class)))
(cl-defmethod phpinspect-project-get-class-create
((project phpinspect-project) (class-fqn phpinspect--type) &optional no-enqueue)
(let ((class (phpinspect-project-get-class project class-fqn)))
(unless class
(phpinspect-project-edit project
(setq class (phpinspect-project-create-class project class-fqn))
(unless no-enqueue
(phpinspect-project-enqueue-if-not-present project class-fqn))))
class))
(cl-defmethod phpinspect-project-get-class-extra-or-create
((project phpinspect-project) (class-fqn phpinspect--type) &optional no-enqueue)
(or (phpinspect-project-get-class-or-extra project class-fqn)
(phpinspect-project-get-class-create project class-fqn no-enqueue)))
(defalias 'phpinspect-project-add-class-if-missing #'phpinspect-project-get-class-create)
(cl-defmethod phpinspect-project-get-class
((project phpinspect-project) (class-fqn phpinspect--type))
"Get indexed class by name of CLASS-FQN stored in PROJECT."
(let ((class (gethash (phpinspect--type-name-symbol class-fqn)
(phpinspect-project-class-index project))))
(when (and class (phpinspect-project-read-only-p project)
(not (phpinspect--class-read-only-p class)))
(setf (phpinspect--class-read-only-p class) t))
class))
(cl-defmethod phpinspect-project-get-class-or-extra
((project phpinspect-project) (class-fqn phpinspect--type))
(or (phpinspect-project-get-class project class-fqn)
(and (phpinspect-project-extra-class-retriever project)
(funcall (phpinspect-project-extra-class-retriever project)
class-fqn))))
(cl-defmethod phpinspect-project-get-type-filepath
((project phpinspect-project) (type phpinspect--type) &optional index-new)
"Retrieve filepath to TYPE definition file.
when INDEX-NEW is non-nil, new files are added to the index
before the search is executed."
(let* ((autoloader (phpinspect-project-autoload project)))
(when (eq index-new 'index-new)
(phpinspect-project-edit project
(phpinspect-autoloader-refresh autoloader)))
(let* ((result (phpinspect-autoloader-resolve
autoloader (phpinspect--type-name-symbol type))))
(if (not result)
;; Index new files and try again if not done already.
(if (eq index-new 'index-new)
nil
(when phpinspect-auto-reindex
(phpinspect--log "Failed finding filepath for type %s. Retrying with reindex."
(phpinspect--type-name type))
(phpinspect-project-get-type-filepath project type 'index-new)))
result))))
(cl-defmethod phpinspect-project-index-type-file
((project phpinspect-project) (type phpinspect--type))
"Index the file that TYPE is expected to be defined in."
(condition-case error
(let* ((file (phpinspect-project-get-type-filepath project type))
(visited-buffer (when file (find-buffer-visiting file))))
(when file
(if visited-buffer
(with-current-buffer visited-buffer (phpinspect-index-current-buffer))
(with-temp-buffer (phpinspect-project-index-file project file)))))
(file-missing
(phpinspect--log "Failed to find file for type %s: %s" type error)
nil)))
(cl-defmethod phpinspect-project-index-file
((project phpinspect-project) (filename string))
"Index "
(let ((fs (phpinspect-project-fs project)))
(with-temp-buffer
(phpinspect-fs-insert-file-contents fs filename 'prefer-async)
(phpinspect-index-current-buffer))))
(cl-defmethod phpinspect-project-add-file-index ((project phpinspect-project) (filename string))
(phpinspect-project-add-index project (phpinspect-project-index-file project filename)))
(defcustom phpinspect-projects nil
"PHPInspect Projects."
:type '(alist :key-type string
:value-type (alist :key-type symbol
:options ((include-dirs (repeat string)))))
:group 'phpinspect)
(defun phpinspect-project-make-file-indexer (project)
(lambda (filename)
(phpinspect-project-add-file-index project filename)))
(defun phpinspect-project-make-root-resolver (project)
(lambda () (phpinspect-project-root project)))
(defun phpinspect-project-make-class-retriever (project)
(lambda (type)
(or (phpinspect-project-get-class-or-extra project type)
(phpinspect-project-get-class-create project type))))
;;; 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-defmethod phpinspect-make-index-task ((project phpinspect-project)
(type phpinspect--type))
(phpinspect-make-index-task-generated
:project project
:type type))
(cl-defmethod phpinspect-task-project ((task phpinspect-index-task))
(phpinspect-index-task-project task))
(cl-defmethod phpinspect-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
(phpinspect--type= (phpinspect-index-task-type task1) (phpinspect-index-task-type task2))))
(cl-defmethod phpinspect-task-execute ((task phpinspect-index-task)
(worker phpinspect-worker))
"Execute index TASK for WORKER."
(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 as task."
(phpinspect-index-task-type task)
(phpinspect-project-root project))
(cond (is-native-type
(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
;; and skipped, as we haven't done any intensive work that
;; may cause hangups.
(setf (phpinspect-worker-skip-next-pause worker) t))
(t
(let* ((type (phpinspect-index-task-type task))
(root-index (phpinspect-project-index-type-file project type)))
(when root-index
(phpinspect-project-add-index project root-index)))))))
;;; INDEX FILE TASK
(cl-defstruct (phpinspect-index-dir-task (:constructor phpinspect-make-index-dir-task))
"A task for the indexation of files"
(project nil
:type phpinspect-project)
(dir nil
:type string))
(cl-defmethod phpinspect-task=
((task1 phpinspect-index-dir-task) (task2 phpinspect-index-dir-task))
(and (eq (phpinspect-index-dir-task-project task1)
(phpinspect-index-dir-task-project task2))
(string= (phpinspect-index-dir-task-dir task1)
(phpinspect-index-dir-task-dir task2))))
(cl-defmethod phpinspect-task-project ((task phpinspect-index-dir-task))
(phpinspect-index-dir-task-project task))
(cl-defmethod phpinspect-task-execute ((task phpinspect-index-dir-task)
(_worker phpinspect-worker))
(phpinspect--log "Entering..")
(let* ((project (phpinspect-index-dir-task-project task))
(fs (phpinspect-project-fs project))
(dir (phpinspect-index-dir-task-dir task)))
(phpinspect--log "Indexing directory %s" dir)
(phpinspect-pipeline (phpinspect-fs-directory-files-recursively fs dir "\\.php$")
:into (phpinspect-project-add-file-index :with-context project))))
(provide 'phpinspect-project)
;;; phpinspect-project.el ends here

@ -1,129 +0,0 @@
;;; phpinspect-queue.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-queue
(:constructor phpinspect-make-queue-generated))
(-first nil
:type phpinspect-queue-item
:documentation
"The first item in the queue")
(-last nil
:type phpinspect-queue-item
:documentation
"The last item in the queue")
(subscription nil
:type function
:documentation
"A function that should be called when items are
enqueued."))
(cl-defstruct (phpinspect-queue-item
(:constructor phpinspect-make-queue-item))
(next nil
:type phpinspect-queue-item
:documentation
"The next item in the queue")
(value nil
:type any
:documentation
"The value stored in the queue")
(previous nil
:type phpinspect-queue-item
:documentation
"The previous item in the queue"))
(define-inline phpinspect-make-queue (&optional subscription)
(inline-quote
(progn
(phpinspect-make-queue-generated :subscription ,subscription))))
(cl-defmethod phpinspect-queue-first ((queue phpinspect-queue))
(phpinspect-queue--first queue))
(cl-defmethod phpinspect-queue-last ((queue phpinspect-queue))
(or (phpinspect-queue--last queue) (phpinspect-queue--first queue)))
(cl-defmethod phpinspect-queue-enqueue ((queue phpinspect-queue) value &optional no-notify)
"Add VALUE to the end of the queue that ITEM is part of."
(let ((last (phpinspect-queue-last queue))
(new-item (phpinspect-make-queue-item :value value)))
(if (not last)
(setf (phpinspect-queue--first queue) new-item)
(setf (phpinspect-queue-item-next last) new-item)
(setf (phpinspect-queue-item-previous new-item) last))
(setf (phpinspect-queue--last queue) new-item))
(when (and (not no-notify) (phpinspect-queue-subscription queue))
(funcall (phpinspect-queue-subscription queue))))
(cl-defmethod phpinspect-queue-dequeue ((queue phpinspect-queue))
"Remove the value at the front of the queue that ITEM is part of and return it."
(let* ((first (phpinspect-queue-first queue))
next value)
(when first
(setq next (phpinspect-queue-item-next first))
(setq value (phpinspect-queue-item-value first)))
(if next
(setf (phpinspect-queue-item-previous next) nil)
(setf (phpinspect-queue--last queue) nil))
(setf (phpinspect-queue--first queue) next)
value))
(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 value 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)))
(while ,item-sym
(let ((,place (phpinspect-queue-item-value ,item-sym)))
,@body
(setq ,item-sym (phpinspect-queue-item-next ,item-sym)))))))
(cl-defmethod phpinspect-queue-find
((queue phpinspect-queue) value comparison-func)
"Find VALUE in the queue that ITEM is part of using COMPARISON-FUNC."
(catch 'found
(phpinspect-doqueue (current-value queue)
(when (funcall comparison-func current-value value)
(throw 'found current-value)))))
(cl-defmethod phpinspect-queue-enqueue-noduplicate
((queue phpinspect-queue) value comparison-func)
(when (not (phpinspect-queue-find queue value comparison-func))
(phpinspect-queue-enqueue queue value)))
(provide 'phpinspect-queue)
;;; phpinspect-queue.el ends here

@ -1,500 +0,0 @@
;;; phpinspect-resolve.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-resolvecontext)
(require 'phpinspect-cache)
(require 'phpinspect-type)
(require 'phpinspect-token-predicates)
(cl-defstruct (phpinspect--assignment
(:constructor phpinspect--make-assignment))
(to nil
:type phpinspect-variable
:documentation "The variable that is assigned to")
(from nil
:type phpinspect-token
:documentation "The token that is assigned from"))
(defsubst phpinspect-block-or-list-p (token)
(or (phpinspect-block-p token)
(phpinspect-list-p token)))
(defsubst phpinspect-maybe-assignment-p (token)
"Like `phpinspect-assignment-p', but includes \"as\" barewords as possible tokens."
(or (phpinspect-assignment-p token)
(equal '(:word "as") token)))
(cl-defgeneric phpinspect--find-assignments-in-token (token)
"Find any assignments that are in TOKEN, at top level or nested in blocks"
(when (keywordp (car token))
(setq token (cdr token)))
(let ((assignments)
(blocks-or-lists)
(statements (phpinspect--split-statements token)))
(dolist (statement statements)
(when (seq-find #'phpinspect-maybe-assignment-p statement)
(phpinspect--log "Found assignment statement")
(push statement assignments))
(when (setq blocks-or-lists (seq-filter #'phpinspect-block-or-list-p statement))
(dolist (block-or-list blocks-or-lists)
(phpinspect--log "Found block or list %s" block-or-list)
(let ((local-assignments (phpinspect--find-assignments-in-token block-or-list)))
(dolist (local-assignment (nreverse local-assignments))
(push local-assignment assignments))))))
;; return
(phpinspect--log "Found assignments in token: %s" assignments)
(phpinspect--log "Found statements in token: %s" statements)
assignments))
(defsubst phpinspect-not-assignment-p (token)
"Inverse of applying `phpinspect-assignment-p to TOKEN."
(not (phpinspect-maybe-assignment-p token)))
(defsubst phpinspect-not-comment-p (token)
(not (phpinspect-comment-p token)))
(defun phpinspect--find-assignments-by-predicate (token predicate)
(let ((variable-assignments)
(all-assignments (phpinspect--find-assignments-in-token token)))
(dolist (assignment all-assignments)
(let* ((is-loop-assignment nil)
(left-of-assignment
(seq-filter #'phpinspect-not-comment-p
(seq-take-while #'phpinspect-not-assignment-p assignment)))
(right-of-assignment
(seq-filter
#'phpinspect-not-comment-p
(cdr (seq-drop-while
(lambda (elt)
(if (phpinspect-maybe-assignment-p elt)
(progn
(when (equal '(:word "as") elt)
(phpinspect--log "It's a loop assignment %s" elt)
(setq is-loop-assignment t))
nil)
t))
assignment)))))
(if is-loop-assignment
(when (funcall predicate right-of-assignment)
;; Masquerade as an array access assignment
(setq left-of-assignment (append left-of-assignment '((:array))))
(push (phpinspect--make-assignment :to right-of-assignment
:from left-of-assignment)
variable-assignments))
(when (funcall predicate left-of-assignment)
(push (phpinspect--make-assignment :from right-of-assignment
:to left-of-assignment)
variable-assignments)))))
(phpinspect--log "Returning the thing %s" variable-assignments)
(nreverse variable-assignments)))
(defsubst phpinspect-drop-preceding-barewords (statement)
(while (and statement (phpinspect-word-p (cadr statement)))
(pop statement))
statement)
;; TODO: the use of this function and similar ones should be replaced with code
;; that uses locally injected project objects in stead of retrieving the project
;; object through global variables.
(defsubst phpinspect-get-cached-project-class (project-root class-fqn)
(when project-root
(phpinspect-project-get-class-or-extra
(phpinspect--cache-get-project-create (phpinspect--get-or-create-global-cache)
project-root)
class-fqn)))
(defun phpinspect-get-cached-project-class-methods (project-root class-fqn &optional static)
(phpinspect--log "Getting cached project class methods for %s (%s)"
project-root class-fqn)
(when project-root
(let ((class (phpinspect-get-or-create-cached-project-class
project-root
class-fqn)))
(when class
(phpinspect--log "Retrieved class index, starting method collection %s (%s)"
project-root class-fqn)
(if static
(phpinspect--class-get-static-method-list class)
(phpinspect--class-get-method-list class))))))
(defsubst phpinspect-get-cached-project-class-method-type
(project-root class-fqn method-name)
(when project-root
(let* ((class (phpinspect-get-or-create-cached-project-class project-root class-fqn))
(method))
(when class
(setq method
(phpinspect--class-get-method class (phpinspect-intern-name method-name)))
(when method
(phpinspect--function-return-type method))))))
(defsubst phpinspect-get-cached-project-class-variable-type
(project-root class-fqn variable-name)
(phpinspect--log "Getting cached project class variable type for %s (%s::%s)"
project-root class-fqn variable-name)
(when project-root
(let ((found-variable
(phpinspect--class-get-variable
(phpinspect-get-or-create-cached-project-class project-root class-fqn)
variable-name)))
(when found-variable
(phpinspect--variable-type found-variable)))))
(defsubst phpinspect-get-cached-project-class-static-method-type
(project-root class-fqn method-name)
(when project-root
(let* ((class (phpinspect-get-or-create-cached-project-class project-root class-fqn))
(method))
(when class
(setq method
(phpinspect--class-get-static-method
class
(phpinspect-intern-name method-name)))
(when method
(phpinspect--function-return-type method))))))
(defun phpinspect-get-derived-statement-type-in-block
(resolvecontext statement php-block type-resolver &optional function-arg-list)
"Get type of RESOLVECONTEXT subject in PHP-BLOCK.
Use TYPE-RESOLVER and FUNCTION-ARG-LIST in the process.
An example of a derived statement would be the following php code:
$variable->attribute->method();
$variable->attribute;
$variable->method();
self::method();
ClassName::method();
$variable = ClassName::method();
$variable = $variable->method();"
;; A derived statement can be an assignment itself.
(when (seq-find #'phpinspect-assignment-p statement)
(phpinspect--log "Derived statement is an assignment: %s" statement)
(setq statement (cdr (seq-drop-while #'phpinspect-not-assignment-p statement))))
(phpinspect--log "Get derived statement type in block: %s" statement)
(let* ((first-token (pop statement))
(current-token)
(previous-attribute-type))
;; No first token means we were passed an empty list.
(when (and first-token
(setq previous-attribute-type
(or
;; Statements starting with a bare word can indicate a static
;; method call. These could be statements with "return" or
;; another bare-word at the start though, so we drop preceding
;; barewords when they are present.
(when (phpinspect-word-p first-token)
(when (phpinspect-word-p (car statement))
(setq statement (phpinspect-drop-preceding-barewords
statement))
(setq first-token (pop statement)))
(funcall type-resolver (phpinspect--make-type
:name (cadr first-token))))
;; No bare word, assume we're dealing with a variable.
(when (phpinspect-variable-p first-token)
(phpinspect-get-variable-type-in-block
resolvecontext
(cadr first-token)
php-block
type-resolver
function-arg-list)))))
(phpinspect--log "Statement: %s" statement)
(phpinspect--log "Starting attribute type: %s" previous-attribute-type)
(while (setq current-token (pop statement))
(phpinspect--log "Current derived statement token: %s" current-token)
(cond ((phpinspect-object-attrib-p current-token)
(let ((attribute-word (cadr current-token)))
(when (phpinspect-word-p attribute-word)
(if (phpinspect-list-p (car statement))
(progn
(pop statement)
(setq previous-attribute-type
(or
(phpinspect-get-cached-project-class-method-type
(phpinspect--resolvecontext-project-root
resolvecontext)
(funcall type-resolver previous-attribute-type)
(cadr attribute-word))
previous-attribute-type)))
(setq previous-attribute-type
(or
(phpinspect-get-cached-project-class-variable-type
(phpinspect--resolvecontext-project-root
resolvecontext)
(funcall type-resolver previous-attribute-type)
(cadr attribute-word))
previous-attribute-type))))))
((phpinspect-static-attrib-p current-token)
(let ((attribute-word (cadr current-token)))
(phpinspect--log "Found attribute word: %s" attribute-word)
(phpinspect--log "checking if next token is a list. Token: %s"
(car statement))
(when (phpinspect-word-p attribute-word)
(if (phpinspect-list-p (car statement))
(progn
(pop statement)
(setq previous-attribute-type
(or
(phpinspect-get-cached-project-class-static-method-type
(phpinspect--resolvecontext-project-root
resolvecontext)
(funcall type-resolver previous-attribute-type)
(cadr attribute-word))
previous-attribute-type)))))))
((and previous-attribute-type (phpinspect-array-p current-token))
(setq previous-attribute-type
(or (phpinspect--type-contains previous-attribute-type)
previous-attribute-type)))))
(phpinspect--log "Found derived type: %s" previous-attribute-type)
;; Make sure to always return a FQN
(funcall type-resolver previous-attribute-type))))
;;;;
;; TODO: since we're passing type-resolver to all of the get-variable-type functions now,
;; we may as well always return FQNs in stead of relative type names.
;;;;
(defun phpinspect-get-variable-type-in-block
(resolvecontext variable-name php-block type-resolver &optional function-arg-list)
"Find the type of VARIABLE-NAME in PHP-BLOCK using TYPE-RESOLVER.
Returns either a FQN or a relative type name, depending on
whether or not the root variable of the assignment value (right
side of assignment) can be found in FUNCTION-ARG-LIST.
When PHP-BLOCK belongs to a function, supply FUNCTION-ARG-LIST to
resolve types of function argument variables."
(phpinspect--log "Looking for assignments of variable %s in php block" variable-name)
(if (string= variable-name "this")
(funcall type-resolver (phpinspect--make-type :name "self"))
(phpinspect-get-pattern-type-in-block
resolvecontext (phpinspect--make-pattern :m `(:variable ,variable-name))
php-block type-resolver function-arg-list)))
(defun phpinspect-get-pattern-type-in-block
(resolvecontext pattern php-block type-resolver &optional function-arg-list)
"Find the type of PATTERN in PHP-BLOCK using TYPE-RESOLVER.
PATTERN must be an object of the type `phpinspect--pattern'.
Returns either a FQN or a relative type name, depending on
whether or not the root variable of the assignment value (right
side of assignment) needs to be extracted from FUNCTION-ARG-LIST.
When PHP-BLOCK belongs to a function, supply FUNCTION-ARG-LIST to
resolve types of function argument variables."
(let* ((assignments
(phpinspect--find-assignments-by-predicate
php-block (phpinspect--pattern-matcher pattern)))
(last-assignment (when assignments (car (last assignments))))
(last-assignment-value (when last-assignment
(phpinspect--assignment-from last-assignment)))
(pattern-code (phpinspect--pattern-code pattern))
(result))
(phpinspect--log "Looking for assignments of pattern %s in php block" pattern-code)
(if (not assignments)
(when (and (= (length pattern-code) 2) (phpinspect-variable-p (cadr pattern-code)))
(let ((variable-name (cadadr pattern-code)))
(progn
(phpinspect--log "No assignments found for variable %s, checking function arguments: %s"
variable-name function-arg-list)
(setq result (phpinspect-get-variable-type-in-function-arg-list
variable-name type-resolver function-arg-list)))))
(setq result
(phpinspect--interpret-expression-type-in-context
resolvecontext php-block type-resolver
last-assignment-value function-arg-list)))
(phpinspect--log "Type interpreted from last assignment expression of pattern %s: %s"
pattern-code result)
(when (and result (phpinspect--type-collection result) (not (phpinspect--type-contains result)))
(phpinspect--log (concat
"Interpreted type %s is a collection type, but 'contains'"
"attribute is not set. Attempting to infer type from context")
result)
(setq result (phpinspect--copy-type result))
(let ((concat-pattern
(phpinspect--pattern-concat
pattern (phpinspect--make-pattern :f #'phpinspect-array-p))))
(phpinspect--log "Inferring type of concatenated pattern %s"
(phpinspect--pattern-code concat-pattern))
(setf (phpinspect--type-contains result)
(phpinspect-get-pattern-type-in-block
resolvecontext concat-pattern php-block
type-resolver function-arg-list))))
; return
result))
(defun phpinspect--split-statements (tokens &optional predicate)
"Split TOKENS into separate statements.
If PREDICATE is provided, it is used as additional predicate to
determine whether a token delimits a statement."
(let ((sublists)
(current-sublist))
(dolist (thing tokens)
(if (or (phpinspect-statement-introduction-p thing)
(when predicate (funcall predicate thing)))
(when current-sublist
(when (phpinspect-block-p thing)
(push thing current-sublist))
(push (nreverse current-sublist) sublists)
(setq current-sublist nil))
(push thing current-sublist)))
(when current-sublist
(push (nreverse current-sublist) sublists))
(nreverse sublists)))
(defun phpinspect-get-variable-type-in-function-arg-list (variable-name type-resolver arg-list)
"Infer VARIABLE-NAME's type from typehints in
ARG-LIST. ARG-LIST should be a list token as returned by
`phpinspect--list-handler` (see also `phpinspect-list-p`)"
(let ((arg-no (seq-position arg-list
variable-name
(lambda (token variable-name)
(and (phpinspect-variable-p token)
(string= (car (last token)) variable-name))))))
(if (and arg-no
(> arg-no 0))
(let ((arg (elt arg-list (- arg-no 1))))
(if (phpinspect-word-p arg)
(funcall type-resolver
(phpinspect--make-type :name (car (last arg))))
nil)))))
(defun phpinspect--interpret-expression-type-in-context
(resolvecontext php-block type-resolver expression &optional function-arg-list)
"Infer EXPRESSION's type from provided context.
Use RESOLVECONTEXT, PHP-BLOCK, TYPE-RESOLVER and
FUNCTION-ARG-LIST as contextual information to infer type of
EXPRESSION."
;; When the right of an assignment is more than $variable; or "string";(so
;; (:variable "variable") (:terminator ";") or (:string "string") (:terminator ";")
;; in tokens), we're likely working with a derived assignment like $object->method()
;; or $object->attributen
(cond ((phpinspect-array-p (car expression))
(let ((collection-contains)
(collection-items (phpinspect--split-statements (cdr (car expression))))
(count 0))
(phpinspect--log "Checking collection items: %s" collection-items)
(while (and (< count (length collection-items))
(not collection-contains))
(setq collection-contains
(phpinspect--interpret-expression-type-in-context
resolvecontext php-block type-resolver
(elt collection-items count) function-arg-list)
count (+ count 1)))
(phpinspect--log "Collection contained: %s" collection-contains)
(phpinspect--make-type :name "\\array"
:fully-qualified t
:collection t
:contains collection-contains)))
((and (phpinspect-word-p (car expression))
(string= (cadar expression) "new"))
(funcall
type-resolver (phpinspect--make-type :name (cadadr expression))))
((and (> (length expression) 1)
(seq-find (lambda (part) (or (phpinspect-attrib-p part)
(phpinspect-array-p part)))
expression))
(phpinspect--log "Variable was assigned with a derived statement")
(phpinspect-get-derived-statement-type-in-block
resolvecontext expression php-block
type-resolver function-arg-list))
;; If the right of an assignment is just $variable;, we can check if it is a
;; function argument and otherwise recurse to find the type of that variable.
((phpinspect-variable-p (car expression))
(phpinspect--log "Variable was assigned with the value of another variable: %s"
expression)
(or (when function-arg-list
(phpinspect-get-variable-type-in-function-arg-list
(cadar expression)
type-resolver function-arg-list))
(phpinspect-get-variable-type-in-block resolvecontext
(cadar expression)
php-block
type-resolver
function-arg-list)))))
(defun phpinspect-resolve-type-from-context (resolvecontext &optional type-resolver)
(unless type-resolver
(setq type-resolver
(phpinspect--make-type-resolver-for-resolvecontext resolvecontext)))
(phpinspect--log "Looking for type of statement: %s in nested token"
(phpinspect--resolvecontext-subject resolvecontext))
;; Find all enclosing tokens that aren't classes. Classes do not contain variable
;; assignments which have effect in the current scope, which is what we're trying
;; to find here to infer the statement type.
(let ((enclosing-tokens (seq-filter #'phpinspect-not-class-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(enclosing-token)
(type))
(while (and enclosing-tokens (not type))
;;(phpinspect--log "Trying to find type in %s" enclosing-token)
(setq enclosing-token (pop enclosing-tokens))
(setq type
(cond ((phpinspect-namespace-p enclosing-token)
(phpinspect-get-derived-statement-type-in-block
resolvecontext
(phpinspect--resolvecontext-subject
resolvecontext)
(or (phpinspect-namespace-block enclosing-token)
enclosing-token)
type-resolver))
((or (phpinspect-block-p enclosing-token)
(phpinspect-root-p enclosing-token))
(phpinspect-get-derived-statement-type-in-block
resolvecontext
(phpinspect--resolvecontext-subject
resolvecontext)
enclosing-token
type-resolver))
((phpinspect-function-p enclosing-token)
(phpinspect-get-derived-statement-type-in-block
resolvecontext
(phpinspect--resolvecontext-subject
resolvecontext)
(phpinspect-function-block enclosing-token)
type-resolver
(phpinspect-function-argument-list enclosing-token))))))
type))
(provide 'phpinspect-resolve)

@ -1,231 +0,0 @@
;;; phpinspect-resolvecontext.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-cache)
(require 'phpinspect-project)
(require 'phpinspect-token-predicates)
(require 'phpinspect-type)
(require 'phpinspect-meta)
(require 'phpinspect-util)
(defsubst phpinspect-blocklike-p (token)
(or (phpinspect-block-p token)
(phpinspect-function-p token)
(phpinspect-class-p token)
(phpinspect-namespace-p token)))
(defsubst phpinspect-return-p (token)
(and (phpinspect-word-p token)
(string= "return" (cadr token))))
(define-inline phpinspect-statement-introduction-p (token)
(inline-letevals (token)
(inline-quote
(or (phpinspect-return-p ,token)
(phpinspect-end-of-statement-p ,token)
(phpinspect-function-p ,token)))))
(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-metadata nil
:type list
:documentation
"Metadata of tokens that enclose the subject.")
(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-find-statement-before-point (bmap meta point)
(let ((children (reverse (phpinspect-meta-find-children-before meta point)))
token previous-siblings)
(catch 'return
(dolist (child children)
(setq token (phpinspect-meta-token child))
(when (< (phpinspect-meta-start child) point)
(if (and (not previous-siblings) (phpinspect-blocklike-p token))
(progn
(throw 'return (phpinspect-find-statement-before-point bmap child point)))
(when (phpinspect-statement-introduction-p token)
(throw 'return previous-siblings))
(unless (phpinspect-comment-p token)
(push child previous-siblings))))))
previous-siblings))
(defun phpinspect--get-last-statement-in-token (token)
(setq token (cond ((phpinspect-function-p token)
(phpinspect-function-block token))
((phpinspect-namespace-p token)
(phpinspect-namespace-block token))
(t token)))
(nreverse
(seq-take-while
(let ((keep-taking t) (last-test nil))
(lambda (elt)
(when last-test
(setq keep-taking nil))
(setq last-test (phpinspect-variable-p elt))
(and keep-taking
(not (phpinspect-end-of-statement-p elt))
(listp elt))))
(reverse token))))
(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))
(phpinspect--log "Last token before point: %s, right siblings: %s, parent: %s"
(phpinspect-meta-string subject)
(mapcar #'phpinspect-meta-token (phpinspect-meta-right-siblings subject))
(phpinspect-meta-string (phpinspect-meta-parent subject)))
(let ((next-sibling (car (phpinspect-meta-right-siblings subject))))
;; When the right sibling of the last ending token overlaps point, this is
;; our actual subject.
(when (and next-sibling
(phpinspect-meta-overlaps-point next-sibling point))
(setq subject next-sibling)))
;; Dig down through tokens that can contain statements
(let (new-subject)
(catch 'break
(while (and subject
(phpinspect-enclosing-token-p (phpinspect-meta-token subject))
(cdr (phpinspect-meta-token subject)))
(setq new-subject (phpinspect-meta-find-child-before subject point))
(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
(mapcar #'phpinspect-meta-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 parent enclosing-tokens))
(setq parent (phpinspect-meta-parent parent))))))
(phpinspect--make-resolvecontext
:subject (phpinspect--get-last-statement-in-token subject-token)
:enclosing-tokens (nreverse (mapcar #'phpinspect-meta-token enclosing-tokens))
:enclosing-metadata (nreverse enclosing-tokens)
:project-root (phpinspect-current-project-root))))
(defun phpinspect--resolvecontext-project (rctx)
(phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect--resolvecontext-project-root rctx)))
(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)))
(cl-defmethod phpinspect--resolvecontext-pop-meta ((rctx phpinspect--resolvecontext))
"Remove the first element of enclosing token metadata and
return it. Pops enclosing tokens to keep both in sync."
(pop (phpinspect--resolvecontext-enclosing-tokens rctx))
(pop (phpinspect--resolvecontext-enclosing-metadata rctx)))
(provide 'phpinspect-resolvecontext)
;;; phpinspect-resolvecontext.el ends here

@ -1,114 +0,0 @@
;;; phpinspect-index.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-type)
(require 'phpinspect-class)
(cl-defgeneric phpinspect--serialize-type (_type)
nil)
(cl-defmethod phpinspect--serialize-type ((type phpinspect--type))
`(phpinspect--make-type
:name ,(phpinspect--type-name type)
:collection ,(phpinspect--type-collection type)
:contains ,(when (phpinspect--type-contains type)
(phpinspect--serialize-type (phpinspect--type-contains type)))
:fully-qualified ,(phpinspect--type-fully-qualified type)))
;; (cl-defmethod phpinspect--serialize-function (_func)
;; nil)
(cl-defmethod phpinspect--serialize-function ((func phpinspect--function))
`(phpinspect--make-function
:name ,(phpinspect--function-name func)
:token (quote ,(phpinspect--function-token func))
:scope (quote ,(phpinspect--function-scope func))
:arguments ,(append '(list)
(mapcar (lambda (arg)
`(list ,(car arg) ,(phpinspect--serialize-type (cadr arg))))
(phpinspect--function-arguments func)))
:return-type ,(when (phpinspect--function-return-type func)
(phpinspect--serialize-type
(phpinspect--function-return-type func)))))
(cl-defmethod phpinspect--serialize-variable ((var phpinspect--variable))
`(phpinspect--make-variable :name ,(phpinspect--variable-name var)
:type ,(when (phpinspect--variable-type var)
(phpinspect--serialize-type
(phpinspect--variable-type var)))
:scope (quote ,(phpinspect--variable-scope var))))
(cl-defmethod phpinspect--serialize-indexed-class ((class (head phpinspect--indexed-class)))
``(phpinspect--indexed-class
(complete . ,,(alist-get 'complete class))
(class-name . ,,(phpinspect--serialize-type (alist-get 'class-name class)))
(declaration . ,(quote ,(alist-get 'declaration class)))
(imports . ,,(append '(list)
(mapcar #'phpinspect--serialize-import
(alist-get 'imports class))))
(methods . ,,(append '(list)
(mapcar #'phpinspect--serialize-function
(alist-get 'methods class))))
(static-methods . ,,(append '(list)
(mapcar #'phpinspect--serialize-function
(alist-get 'static-methods class))))
(static-variables . ,,(append '(list)
(mapcar #'phpinspect--serialize-variable
(alist-get 'static-variables class))))
(variables . ,,(append '(list)
(mapcar #'phpinspect--serialize-variable
(alist-get 'variables class))))
(constants . ,,(append '(list)
(mapcar #'phpinspect--serialize-variable
(alist-get 'constants class))))
(extends . ,,(append '(list)
(mapcar #'phpinspect--serialize-type
(alist-get 'extends class))))
(implements . ,,(append '(list)
(mapcar #'phpinspect--serialize-type
(alist-get 'implements class))))))
(cl-defmethod phpinspect--serialize-root-index ((index (head phpinspect--root-index)))
``(phpinspect--root-index
(imports . ,,(append '(list)
(mapcar #'phpinspect--serialize-import
(alist-get 'imports 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))))))
(defun phpinspect--serialize-import (import)
`(cons
(phpinspect-intern-name ,(symbol-name (car import)))
,(phpinspect--serialize-type (cdr import))))
(provide 'phpinspect-serialize)
;;; phpinspect-serialize.el ends here

@ -1,499 +0,0 @@
;;; phpinspect-splayt.el --- A Splay Tree Implementation -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
;;
;; A splay tree implementation exclusively using `cons' and `list' data
;; structures with the aim to have as low a memory footprint as possible.
;;
;; Important functions:
;; - `phpinspect-splayt-insert'
;; - `phpinspect-splayt-find'
;; - `phpinspect-splayt-find-smallest-after'
;; - `phpinspect-splayt-find-all-after'
;; - `phpinspect-splayt-traverse'
;;;
;;
;; DEVELOPING
;;
;; The main aim for this tree implementation is to be reasonably fast and
;; comfortable to use for most of phpinspect's common operations. That means:
;;
;; - Fast insertion of sequential keys (for example when parsing a buffer from left to right)
;; - Consing as few bytes as possible by keeping the data structure simple, to avoid GC pauses as much as possible
;; - Fast repeated acces of "hot" regions (for example the edited region of a buffer)
;; - A straight forward public API to retrieve sets of nodes
;;
;; ** Inline Functions **
;; There is a lot of use of `define-inline' in this file. Most of these inlines
;; improve performance significantly. This is especially true for smaller
;; inlined functions. It might be possible to change one or two of the larger
;; functions to normal defuns without reintroducing a lot of ovehead. If you
;; want to do this to make debugging the code a little easier, and you can
;; backup that it doesn't impact performance all that much with some benchmarks
;; (especially parse-file.el in the benchmarks folder), your PR will be welcomed.
;;
;;; Code:
(define-inline phpinspect-make-splayt-node (key value &optional left right parent temp-store)
(inline-quote (cons (cons ,key ,value) (list ,left ,right ,parent ,temp-store))))
(define-inline phpinspect-splayt-node-left (node)
(inline-quote (cadr ,node)))
(define-inline phpinspect-splayt-node-right (node)
(inline-quote (caddr ,node)))
(define-inline phpinspect-splayt-node-key (node)
(inline-quote (caar ,node)))
(define-inline phpinspect-splayt-node-value (node)
(inline-quote (cdar ,node)))
(define-inline phpinspect-splayt-node-parent (node)
(inline-quote (cadddr ,node)))
(define-inline phpinspect-splayt-node-temp-store (node)
"Dedicated place to store data when necessary. Mostly used for
rotations without instantiating a lexical environment with
`let'. When only used once or twice in a function call, this
apeared to be a little more performant than using `let'."
(inline-quote (car (cddddr ,node))))
(define-inline phpinspect-splayt-node-update-parent (node parent new-val)
(inline-letevals (node parent new-val)
(inline-quote
(if (eq ,node (phpinspect-splayt-node-left ,parent))
(setf (phpinspect-splayt-node-left ,parent) ,new-val)
(setf (phpinspect-splayt-node-right ,parent) ,new-val)))))
(define-inline phpinspect-make-splayt (&optional root-node)
(inline-quote
(cons 'phpinspect-splayt ,root-node)))
(define-inline phpinspect-splayt-root-node (splayt)
(inline-quote (cdr ,splayt)))
(define-inline phpinspect-splayt-empty-p (splayt)
(inline-quote (not (phpinspect-splayt-root-node ,splayt))))
(define-inline phpinspect-splayt-node-rotate-right (node &optional splayt)
(inline-letevals (node splayt)
(inline-quote
(progn
;; Save right node of left child
(setf (phpinspect-splayt-node-temp-store ,node)
(phpinspect-splayt-node-right (phpinspect-splayt-node-left ,node)))
;; Update node parent to reference left as child
(when (phpinspect-splayt-node-parent ,node)
(phpinspect-splayt-node-update-parent
,node (phpinspect-splayt-node-parent ,node) (phpinspect-splayt-node-left ,node)))
;; Set left node new parent
(setf (phpinspect-splayt-node-parent (phpinspect-splayt-node-left ,node))
(phpinspect-splayt-node-parent ,node))
;; Set left node as node's new parent
(setf (phpinspect-splayt-node-parent ,node)
(phpinspect-splayt-node-left ,node))
;; Set node as left's right child
(setf (phpinspect-splayt-node-right (phpinspect-splayt-node-left ,node)) ,node)
;; Set left's right child as node's left child
(setf (phpinspect-splayt-node-left ,node) (phpinspect-splayt-node-temp-store ,node))
;; Update new left child's parent
(when (phpinspect-splayt-node-left ,node)
(setf (phpinspect-splayt-node-parent (phpinspect-splayt-node-left ,node)) ,node))
;; Update root node of tree when necessary
(when (and ,splayt (eq ,node (phpinspect-splayt-root-node ,splayt)))
(setf (phpinspect-splayt-root-node ,splayt) (phpinspect-splayt-node-parent ,node)))))))
(define-inline phpinspect-splayt-node-rotate-left (node &optional splayt)
(inline-letevals (node splayt)
(inline-quote
(progn
;; Save left node of right child
(setf (phpinspect-splayt-node-temp-store ,node)
(phpinspect-splayt-node-left (phpinspect-splayt-node-right ,node)))
;; Update node parent to reference right as child
(when (phpinspect-splayt-node-parent ,node)
(phpinspect-splayt-node-update-parent
,node (phpinspect-splayt-node-parent ,node) (phpinspect-splayt-node-right ,node)))
;; Set right node new parent
(setf (phpinspect-splayt-node-parent (phpinspect-splayt-node-right ,node))
(phpinspect-splayt-node-parent ,node))
;; Set right node as node's new parent
(setf (phpinspect-splayt-node-parent ,node)
(phpinspect-splayt-node-right ,node))
;; Set node as right's left child
(setf (phpinspect-splayt-node-left (phpinspect-splayt-node-right ,node)) ,node)
;; Set right's left child as node's right child
(setf (phpinspect-splayt-node-right ,node) (phpinspect-splayt-node-temp-store ,node))
;; Update new right child's parent
(when (phpinspect-splayt-node-right ,node)
(setf (phpinspect-splayt-node-parent (phpinspect-splayt-node-right ,node)) ,node))
;; Update root node of tree when necessary
(when (and ,splayt (eq ,node (phpinspect-splayt-root-node ,splayt)))
(setf (phpinspect-splayt-root-node ,splayt) (phpinspect-splayt-node-parent ,node)))))))
(define-inline phpinspect-splayt-node-grandparent (node)
(inline-quote (phpinspect-splayt-node-parent (phpinspect-splayt-node-parent ,node))))
(define-inline phpinspect-splay (splayt node)
(inline-letevals (splayt node)
(let ((parent (inline-quote (phpinspect-splayt-node-parent ,node)))
(grandparent (inline-quote (phpinspect-splayt-node-grandparent ,node))))
(inline-quote
(progn
(while ,parent
(if (phpinspect-splayt-node-grandparent ,node)
(cond
;; Zig-Zig rotation
((and (eq ,parent (phpinspect-splayt-node-left ,grandparent))
(eq ,node (phpinspect-splayt-node-left ,parent)))
(phpinspect-splayt-node-rotate-right ,grandparent ,splayt)
(phpinspect-splayt-node-rotate-right ,parent ,splayt))
;; Zag-Zag rotation
((and (eq (phpinspect-splayt-node-parent ,node)
(phpinspect-splayt-node-right (phpinspect-splayt-node-grandparent ,node)))
(eq ,node (phpinspect-splayt-node-right (phpinspect-splayt-node-parent ,node))))
(phpinspect-splayt-node-rotate-left ,grandparent ,splayt)
(phpinspect-splayt-node-rotate-left ,parent ,splayt))
;; Zig-Zag rotation
((and (eq ,parent (phpinspect-splayt-node-right ,grandparent))
(eq ,node (phpinspect-splayt-node-left ,parent)))
(phpinspect-splayt-node-rotate-right ,parent ,splayt)
(phpinspect-splayt-node-rotate-left ,parent ,splayt))
;; Zag-Zig rotation
((and (eq ,parent (phpinspect-splayt-node-left ,grandparent))
(eq ,node (phpinspect-splayt-node-right ,parent)))
(phpinspect-splayt-node-rotate-left ,parent ,splayt)
(phpinspect-splayt-node-rotate-right ,parent ,splayt))
(t
(error "Failed in determining rotation strategy.")))
;; Else
(if (eq ,node (phpinspect-splayt-node-left ,parent))
(phpinspect-splayt-node-rotate-right ,parent ,splayt)
(phpinspect-splayt-node-rotate-left ,parent ,splayt))))
,node)))))
(define-inline phpinspect-splayt-insert-node (splayt node)
(inline-letevals (splayt node (parent (inline-quote (phpinspect-splayt-node-temp-store ,node))))
(inline-quote
(if (phpinspect-splayt-empty-p ,splayt)
(setf (phpinspect-splayt-root-node ,splayt) ,node)
(progn
(setf ,parent (phpinspect-splayt-find-insertion-node ,splayt (phpinspect-splayt-node-key ,node)))
(unless ,parent
(error "Error: failed to find parent node for %s" ,node))
(setf (phpinspect-splayt-node-parent ,node) ,parent)
(if (< (phpinspect-splayt-node-key ,parent) (phpinspect-splayt-node-key ,node))
(setf (phpinspect-splayt-node-right ,parent) ,node)
(setf (phpinspect-splayt-node-left ,parent) ,node))
(phpinspect-splay ,splayt ,node))))))
(defmacro phpinspect-splayt-node-traverse (place-and-node &rest body)
(declare (indent 1))
(let ((place (car place-and-node))
(current-sym (gensym))
(stack-sym (gensym))
(queue-sym (gensym))
(reverse-sym (gensym))
(node-sym (gensym))
(size-sym (gensym)))
`(let* ((,node-sym ,(cadr place-and-node))
;; Make place locally scoped variable if a symbol
(,queue-sym (when ,node-sym
(list ,node-sym)))
(,reverse-sym t)
,current-sym
,size-sym
,stack-sym)
(while ,queue-sym
(setq ,size-sym (length ,queue-sym))
(while (> ,size-sym 0)
(setq ,current-sym (car (last ,queue-sym))
,queue-sym (butlast ,queue-sym))
(if ,reverse-sym
(push ,current-sym ,stack-sym)
(let ((,place ,current-sym))
,@body))
(when (phpinspect-splayt-node-right ,current-sym)
(push (phpinspect-splayt-node-right ,current-sym) ,queue-sym))
(when (phpinspect-splayt-node-left ,current-sym)
(push (phpinspect-splayt-node-left ,current-sym) ,queue-sym))
(setq ,size-sym (- ,size-sym 1)))
(when ,reverse-sym
(while ,stack-sym
(setq ,current-sym (pop ,stack-sym))
(let ((,place ,current-sym))
,@body)))
(setq ,reverse-sym (not ,reverse-sym)))
nil)))
(defmacro phpinspect-splayt-traverse (place-and-splayt &rest body)
"Traverse SPLAYT, executing BODY.
The PLACE is assigned the value of each node.
Traversal is breadth-first to take advantage of the splay trees'
main benefit: the most accessed interval of keys is likely to be
near the top of the tee.
(fn (PLACE SPLAYT) BODY...)"
(declare (indent 1))
`(phpinspect-splayt-node-traverse
(,(car place-and-splayt) (phpinspect-splayt-root-node ,(cadr place-and-splayt)))
(setf ,(car place-and-splayt) (phpinspect-splayt-node-value ,(car place-and-splayt)))
,@body))
(defmacro phpinspect-splayt-node-traverse-lr (place-and-node &rest body)
(declare (indent 1))
(let ((place (car place-and-node))
(current (gensym))
(stack (gensym)))
`(let* ((,current ,(cadr place-and-node))
,stack)
(while (or ,stack ,current)
(if ,current
(progn
(push ,current ,stack)
(setq ,current (phpinspect-splayt-node-left ,current)))
(setq ,current (pop ,stack))
(let ((,place ,current))
,@body)
(setq ,current (phpinspect-splayt-node-right ,current)))))))
(defmacro phpinspect-splayt-traverse-lr (place-and-splayt &rest body)
"Traverse SPLAYT depth-first from left to right,executing BODY.
The PLACE is assigned the value of each node.
(fn (PLACE SPLAYT) BODY...)"
(declare (indent 1))
`(phpinspect-splayt-node-traverse-lr
(,(car place-and-splayt) (phpinspect-splayt-root-node ,(cadr place-and-splayt)))
(let ((,(car place-and-splayt) (phpinspect-splayt-node-value ,(car place-and-splayt))))
,@body)))
(define-inline phpinspect-splayt-node-key-less-p (node key)
(inline-quote (> ,key (phpinspect-splayt-node-key ,node))))
(define-inline phpinspect-splayt-node-key-le-p (node key)
(inline-quote (>= ,key (phpinspect-splayt-node-key ,node))))
(define-inline phpinspect-splayt-node-key-equal-p (node key)
(inline-quote (= ,key (phpinspect-splayt-node-key ,node))))
(define-inline phpinspect-splayt-node-key-greater-p (node key)
(inline-quote (< ,key (phpinspect-splayt-node-key ,node))))
(define-inline phpinspect-splayt-node-key-ge-p (node key)
(inline-quote (<= ,key (phpinspect-splayt-node-key ,node))))
(define-inline phpinspect-splayt-find-node (splayt key)
(inline-letevals (splayt key)
(inline-quote
(let ((current (phpinspect-splayt-root-node ,splayt)))
(catch 'return
(while current
(if (= ,key (phpinspect-splayt-node-key current))
(progn
(phpinspect-splay ,splayt current)
(throw 'return current))
(if (phpinspect-splayt-node-key-greater-p current ,key)
(setq current (phpinspect-splayt-node-left current))
(setq current (phpinspect-splayt-node-right current))))))))))
(define-inline phpinspect-splayt-find-insertion-node (splayt key)
(inline-letevals (splayt key)
(inline-quote
(let ((current (phpinspect-splayt-root-node ,splayt)))
(catch 'return
(while current
(if (or (and (phpinspect-splayt-node-key-greater-p current ,key)
(not (phpinspect-splayt-node-left current)))
(and (phpinspect-splayt-node-key-le-p current ,key)
(not (phpinspect-splayt-node-right current))))
(throw 'return current)
(if (< ,key (phpinspect-splayt-node-key current))
(setq current (phpinspect-splayt-node-left current))
(setq current (phpinspect-splayt-node-right current))))))))))
(define-inline phpinspect-splayt-find-smallest-node-after (splayt key)
(inline-letevals (splayt key)
(inline-quote
(let ((current (phpinspect-splayt-root-node ,splayt))
smallest)
(catch 'break
(while current
(cond
((phpinspect-splayt-node-key-greater-p current ,key)
(when (and smallest
(phpinspect-splayt-node-key-greater-p
current (phpinspect-splayt-node-key smallest)))
(throw 'break nil))
(setf smallest current
current (phpinspect-splayt-node-left current)))
((phpinspect-splayt-node-right current)
(setf current (phpinspect-splayt-node-right current)))
(t (throw 'break nil)))))
smallest))))
(define-inline phpinspect-splayt-find-largest-node-before (splayt key)
(inline-letevals (splayt key)
(inline-quote
(let ((current (phpinspect-splayt-root-node ,splayt))
largest)
(catch 'break
(while current
(cond
((and (phpinspect-splayt-node-key-less-p current ,key))
(when (and largest
(phpinspect-splayt-node-key-less-p
current (phpinspect-splayt-node-key largest)))
(throw 'break nil))
(setf largest current
current (phpinspect-splayt-node-right current)))
((phpinspect-splayt-node-left current)
(setf current (phpinspect-splayt-node-left current)))
(t (throw 'break nil)))))
largest))))
(defun phpinspect-splayt-find-all-after (splayt key)
"Find all values in SPLAYT with a key higher than KEY."
(let* ((first (phpinspect-splayt-find-smallest-node-after splayt key))
(all (cons nil nil))
(all-rear all))
(while first
(setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value first) nil)))
(phpinspect-splayt-node-traverse (sibling (phpinspect-splayt-node-right first))
(setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value sibling) nil))))
(if (and (phpinspect-splayt-node-parent first)
(eq first (phpinspect-splayt-node-left (phpinspect-splayt-node-parent first))))
(setq first (phpinspect-splayt-node-parent first))
(setq first nil)))
(cdr all)))
(defun phpinspect-splayt-find-all-between (splayt key-min key-max)
(let* ((first (phpinspect-splayt-find-smallest-node-after splayt key-min))
(all (cons nil nil))
(all-rear all))
(catch 'return
(while first
(setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value first) nil)))
(phpinspect-splayt-node-traverse-lr (sibling (phpinspect-splayt-node-right first))
(when (>= (phpinspect-splayt-node-key sibling) key-max)
(throw 'return (cdr all)))
(setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value sibling) nil))))
(if (and (phpinspect-splayt-node-parent first)
(eq first (phpinspect-splayt-node-left (phpinspect-splayt-node-parent first))))
(setq first (phpinspect-splayt-node-parent first))
(setq first nil)))
(cdr all))))
(defun phpinspect-splayt-find-all-before (splayt key)
"Find all values in SPLAYT with a key higher than KEY."
(let* ((first (phpinspect-splayt-find-largest-node-before splayt key))
(all (cons nil nil))
(all-rear all))
(while first
(setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value first) nil)))
(phpinspect-splayt-node-traverse (sibling (phpinspect-splayt-node-left first))
(setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value sibling) nil))))
(if (and (phpinspect-splayt-node-parent first)
(eq first (phpinspect-splayt-node-right (phpinspect-splayt-node-parent first))))
(setq first (phpinspect-splayt-node-parent first))
(setq first nil)))
(cdr all)))
(defun phpinspect-splayt-to-list (tree)
"Convert TREE to an ordered list."
(let* ((list (cons nil nil))
(rear list))
(phpinspect-splayt-traverse-lr (val tree)
(setq rear (setcdr rear (cons val nil))))
(cdr list)))
(cl-defmethod seq-do (func (tree (head phpinspect-splayt)))
(phpinspect-splayt-traverse-lr (val tree)
(funcall func val)))
(defun phpinspect-splayt-find-smallest-after (splayt key)
"Find value of node with smallest key that is higher than KEY in SPLAYT."
(phpinspect-splayt-node-value
(phpinspect-splay
splayt (phpinspect-splayt-find-smallest-node-after splayt key))))
(defun phpinspect-splayt-find-largest-before (splayt key)
"Find value of node with smallest key that is higher than KEY in SPLAYT."
(phpinspect-splayt-node-value
(phpinspect-splay
splayt (phpinspect-splayt-find-largest-node-before splayt key))))
(defun phpinspect-splayt-find (splayt key)
(phpinspect-splayt-node-value (phpinspect-splayt-find-node splayt key)))
(defun phpinspect-splayt-insert (tree key value)
"Insert KEY as VALUE into TREE."
(phpinspect-splayt-insert-node tree (phpinspect-make-splayt-node key value)))
(provide 'phpinspect-splayt)

@ -1,144 +0,0 @@
;;; phpinspect-suggest.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-resolvecontext)
(require 'phpinspect-resolve)
(require 'phpinspect-token-predicates)
(require 'phpinspect-type)
(require 'phpinspect-project)
(require 'phpinspect-class)
(defun phpinspect-suggest-functions (rctx)
(let* ((project (phpinspect--resolvecontext-project rctx)))
(phpinspect-project-get-functions-with-extra project)))
(defun phpinspect-suggest-variables-at-point (resolvecontext)
(phpinspect--log "Suggesting variables at point")
(let ((variables))
(dolist (token (phpinspect--resolvecontext-enclosing-tokens resolvecontext))
(when (phpinspect-not-class-p token)
(let ((token-list token)
(potential-variable))
(while token-list
(setq potential-variable (pop token-list))
(cond ((phpinspect-variable-p potential-variable)
(phpinspect--log "Pushing variable %s" potential-variable)
(push (phpinspect--make-variable
:name (cadr potential-variable)
:type phpinspect--null-type)
variables))
((phpinspect-function-p potential-variable)
(push (phpinspect-function-block potential-variable) token-list)
(dolist (argument (phpinspect-function-argument-list potential-variable))
(when (phpinspect-variable-p argument)
(push (phpinspect--make-variable
:name (cadr argument)
:type phpinspect--null-type)
variables))))
((phpinspect-block-p potential-variable)
(dolist (nested-token (cdr potential-variable))
(push nested-token token-list))))))))
;; Only return variables that have a name. Unnamed variables are just dollar
;; signs (:
(seq-filter #'phpinspect--variable-name variables)))
(defun phpinspect-get-cached-project-class-methods (project-root class-fqn &optional static)
(phpinspect--log "Getting cached project class methods for %s (%s)"
project-root class-fqn)
(when project-root
(let ((class (phpinspect-get-or-create-cached-project-class
project-root
class-fqn)))
(phpinspect--log (if class
"Retrieved class index, starting method collection %s (%s)"
"No class index found in %s for %s")
project-root class-fqn)
(when class
(if static
(phpinspect--class-get-static-method-list class)
(phpinspect--class-get-method-list class))))))
(defun phpinspect--get-methods-for-class
(resolvecontext class &optional static)
"Find all known cached methods for CLASS."
(or (phpinspect-get-cached-project-class-methods
(phpinspect--resolvecontext-project-root resolvecontext)
class static)
(progn (phpinspect--log "Failed to find methods for class %s :(" class) nil)))
(defun phpinspect--get-variables-for-class (class-name &optional static)
(let ((class (phpinspect-get-or-create-cached-project-class
(phpinspect-current-project-root)
class-name)))
(when class
(if static
(append (phpinspect--class-get-static-variables class) (phpinspect--class-get-constants class))
(phpinspect--class-get-variables class)))))
(defun phpinspect--make-method-lister (resolvecontext &optional static)
(lambda (fqn)
(phpinspect--get-methods-for-class resolvecontext fqn static)))
(defun phpinspect-suggest-attributes-at-point
(resolvecontext &optional static)
"Suggest object or class attributes at point.
RESOLVECONTEXT must be a structure of the type
`phpinspect--resolvecontext'. The PHP type of its subject is
resolved to provide completion candidates.
If STATIC is non-nil, candidates are provided for constants,
static variables and static methods."
;; Strip away the existing (incomplete) attribute token. Otherwise, resolving
;; a type from this context while the user has already typed part of an
;; attribute name could return the type of an existing attribute that matches
;; the incomplete name. (this could for example result in methods of the type
;; of $this->entity to be suggested when we really want more suggestions for
;; attributes of the type $this like $this->entityRepository). Essentially, we
;; convert the subject $this->entity into $this so that only the type of $this
;; (or whatever comes before the attribute accessor token (-> or ::)) is
;; actually resolved.
(when (phpinspect-attrib-p (car (last (phpinspect--resolvecontext-subject resolvecontext))))
(setf (phpinspect--resolvecontext-subject resolvecontext)
(butlast (phpinspect--resolvecontext-subject resolvecontext))))
(let* ((type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext))
(method-lister (phpinspect--make-method-lister
resolvecontext
static)))
(let ((statement-type (phpinspect-resolve-type-from-context
resolvecontext
type-resolver)))
(when statement-type
(let ((type (funcall type-resolver statement-type)))
(append (phpinspect--get-variables-for-class
type
static)
(funcall method-lister type)))))))
(provide 'phpinspect-suggest)

@ -1,83 +0,0 @@
;;; phpinspect-toc.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-splayt)
(eval-when-compile
(require 'phpinspect-meta))
(defun phpinspect-make-toc (&optional tree)
(let ((table (make-hash-table :test #'eq :size 20 :rehash-size 2.0)))
(if tree
(phpinspect-splayt-traverse-lr (meta tree)
(puthash (phpinspect-meta-token meta) meta table))
(setq tree (phpinspect-make-splayt)))
(list tree table)))
(define-inline phpinspect-toc-register (toc meta)
(inline-letevals (toc meta)
(inline-quote
(progn
(phpinspect-splayt-insert (phpinspect-toc-tree ,toc) (phpinspect-meta-start ,meta) ,meta)
(puthash (phpinspect-meta-token ,meta) ,meta (phpinspect-toc-table ,toc))))))
(define-inline phpinspect-toc-tree (toc)
(inline-quote (car ,toc)))
(define-inline phpinspect-toc-table (toc)
(inline-quote (cadr ,toc)))
(defun phpinspect-toc-update (toc new-tree current-root)
(let ((current-tree (phpinspect-toc-tree toc))
(new-table (make-hash-table :test #'eq :size 20 :rehash-size 2.0))
new deleted)
(phpinspect-splayt-traverse-lr (meta new-tree)
(puthash (phpinspect-meta-token meta) meta new-table)
(push meta new))
(phpinspect-splayt-traverse-lr (meta current-tree)
(if (eq (phpinspect-meta-find-root meta) current-root)
(progn
(phpinspect-splayt-insert new-tree (phpinspect-meta-start meta) meta)
(puthash (phpinspect-meta-token meta) meta new-table))
(push meta deleted)))
(setf (phpinspect-toc-tree toc) new-tree)
(setf (phpinspect-toc-table toc) new-table)
(list new deleted)))
(defun phpinspect-toc-token-at-point (toc point)
(let ((result (phpinspect-splayt-find-largest-before (phpinspect-toc-tree toc) (+ point 1))))
(and result (phpinspect-meta-overlaps-point result point) result)))
(defun phpinspect-toc-token-at-or-after-point (toc point)
(phpinspect-splayt-find-smallest-after (phpinspect-toc-tree toc) (- point 1)))
(defun phpinspect-toc-tokens-in-region (toc start end)
(phpinspect-splayt-find-all-between (phpinspect-toc-tree toc) start end))
(provide 'phpinspect-toc)

@ -1,254 +0,0 @@
;;; phpinspect-token-predicates.el --- Predicates for phpinspect-parser tokens types -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
(define-inline phpinspect-token-type-p (object type)
"Returns t if OBJECT is a token of type TYPE.
Type can be any of the token types returned by
`phpinspect-parse-buffer-until-point`"
(inline-letevals (object)
(inline-quote
(and (listp ,object) (eq (car ,object) ,type)))))
(defsubst phpinspect-object-attrib-p (token)
(phpinspect-token-type-p token :object-attrib))
(defsubst phpinspect-static-attrib-p (token)
(phpinspect-token-type-p token :static-attrib))
(defsubst phpinspect-attrib-p (token)
(or (phpinspect-object-attrib-p token)
(phpinspect-static-attrib-p token)))
(defun phpinspect-html-p (token)
(phpinspect-token-type-p token :html))
(defun phpinspect-comma-p (token)
(phpinspect-token-type-p token :comma))
(defsubst phpinspect-terminator-p (token)
(phpinspect-token-type-p token :terminator))
(defsubst phpinspect-end-of-token-p (token)
(or (phpinspect-terminator-p token)
(phpinspect-comma-p token)
(phpinspect-html-p token)))
(defsubst phpinspect-incomplete-block-p (token)
(phpinspect-token-type-p token :incomplete-block))
(defsubst phpinspect-block-p (token)
(or (phpinspect-token-type-p token :block)
(phpinspect-incomplete-block-p token)))
(defsubst phpinspect-end-of-statement-p (token)
(or (phpinspect-end-of-token-p token)
(phpinspect-block-p token)))
(defun phpinspect-end-of-use-p (token)
(or (phpinspect-block-p token)
(phpinspect-end-of-token-p token)))
(defun phpinspect-static-p (token)
(phpinspect-token-type-p token :static))
(defsubst phpinspect-incomplete-const-p (token)
(phpinspect-token-type-p token :incomplete-const))
(defsubst phpinspect-const-p (token)
(or (phpinspect-token-type-p token :const)
(phpinspect-incomplete-const-p token)))
(define-inline phpinspect-scope-p (token)
(inline-letevals (token)
(inline-quote
(or (phpinspect-token-type-p ,token :public)
(phpinspect-token-type-p ,token :private)
(phpinspect-token-type-p ,token :protected)))))
(define-inline phpinspect-namespace-p (object)
(inline-quote
(phpinspect-token-type-p ,object :namespace)))
(defun phpinspect-incomplete-class-p (token)
(and (phpinspect-class-p token)
(phpinspect-incomplete-block-p (car (last token)))))
(defun phpinspect-incomplete-namespace-p (token)
(and (phpinspect-namespace-p token)
(or (phpinspect-incomplete-block-p (car (last token)))
(phpinspect-incomplete-class-p (car (last token))))))
(define-inline phpinspect-function-p (token)
(inline-quote (phpinspect-token-type-p ,token :function)))
(define-inline phpinspect-class-p (token)
(inline-quote (phpinspect-token-type-p ,token :class)))
(defun phpinspect-incomplete-method-p (token)
(or (phpinspect-incomplete-function-p token)
(and (phpinspect-scope-p token)
(phpinspect-incomplete-function-p (car (last token))))
(and (phpinspect-scope-p token)
(phpinspect-static-p (car (last token)))
(phpinspect-incomplete-function-p (car (last (car (last token))))))
(and (phpinspect-scope-p token)
(phpinspect-function-p (car (last token))))))
(defun phpinspect-incomplete-function-p (token)
(and (phpinspect-function-p token)
(phpinspect-incomplete-block-p (car (last token)))))
(defsubst phpinspect-incomplete-list-p (token)
(phpinspect-token-type-p token :incomplete-list))
(defsubst phpinspect-list-p (token)
(or (phpinspect-token-type-p token :list)
(phpinspect-incomplete-list-p token)))
(define-inline phpinspect-declaration-p (token)
(inline-quote
(phpinspect-token-type-p ,token :declaration)))
(defsubst phpinspect-assignment-p (token)
(phpinspect-token-type-p token :assignment))
(defun phpinspect-function-argument-list (php-func)
"Get the argument list of a function"
(seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil))
(defun phpinspect-annotation-p (token)
(phpinspect-token-type-p token :annotation))
(defun phpinspect-method-annotation-p (token)
(phpinspect-token-type-p token :method-annotation))
(defun phpinspect-var-annotation-p (token)
(phpinspect-token-type-p token :var-annotation))
(defun phpinspect-return-annotation-p (token)
(phpinspect-token-type-p token :return-annotation))
(define-inline phpinspect-class-variable-p (token)
(inline-quote (phpinspect-token-type-p ,token :class-variable)))
(define-inline phpinspect-variable-p (token)
(inline-letevals (token)
(inline-quote
(or (phpinspect-token-type-p ,token :variable)
(phpinspect-token-type-p ,token :class-variable)))))
(defsubst phpinspect-word-p (token)
(phpinspect-token-type-p token :word))
(defsubst phpinspect-incomplete-array-p (token)
(phpinspect-token-type-p token :incomplete-array))
(defsubst phpinspect-array-p (token)
(or (phpinspect-token-type-p token :array)
(phpinspect-incomplete-array-p token)))
(defsubst phpinspect-root-p (object)
(phpinspect-token-type-p object :root))
(defun phpinspect-incomplete-token-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)
(phpinspect-incomplete-const-p token)
(phpinspect-incomplete-function-p token)
(phpinspect-incomplete-method-p token)
(phpinspect-incomplete-namespace-p token)))
(defun phpinspect-incomplete-root-p (token)
(and (phpinspect-root-p token)
(seq-find #'phpinspect-incomplete-token-p (cdr token))))
(defun phpinspect--static-terminator-p (token)
(or (phpinspect-function-p token)
(phpinspect-end-of-token-p token)))
(defun phpinspect--scope-terminator-p (token)
(or (phpinspect-function-p token)
(phpinspect-end-of-token-p token)
(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")))
(defun phpinspect-use-keyword-p (token)
(and (phpinspect-word-p token) (string= (car (last token)) "use")))
(defsubst phpinspect-namespace-or-root-p (object)
(or (phpinspect-namespace-p object)
(phpinspect-root-p object)))
(define-inline phpinspect-use-p (object)
(inline-quote (phpinspect-token-type-p ,object :use)))
(defun phpinspect-comment-p (token)
(or (phpinspect-token-type-p token :comment)
(phpinspect-token-type-p token :doc-block)))
(defsubst phpinspect-class-block (class)
(caddr class))
(define-inline phpinspect-namespace-is-blocked-p (namespace)
(inline-letevals (namespace)
(inline-quote
(and (= (length ,namespace) 3) (phpinspect-block-p (caddr ,namespace))))))
(defsubst phpinspect-namespace-block (namespace)
(when (phpinspect-namespace-is-blocked-p namespace)
(caddr namespace)))
(defsubst phpinspect-function-block (php-func)
(caddr php-func))
(defsubst phpinspect-not-class-p (token)
"Apply inverse of `phpinspect-class-p' to TOKEN."
(not (phpinspect-class-p token)))
(defsubst phpinspect-probably-token-p (token)
(and (listp token)
(keywordp (car token))))
(provide 'phpinspect-token-predicates)

@ -1,364 +0,0 @@
;;; phpinspect-type.el --- Data structures that represent phpinspect types -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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-util)
(require 'phpinspect-token-predicates)
(eval-when-compile
(require 'phpinspect-parser))
(cl-defstruct (phpinspect--type
(:constructor phpinspect--make-type-generated)
(:copier phpinspect--copy-type))
"Represents an instance of a PHP type in the phpinspect syntax tree."
(name-symbol nil
:type symbol
:documentation
"Symbol representation of the type name.")
(collection nil
:type bool
:documentation
"Whether or not the type is a collection")
(contains nil
:type phpinspect--type
:documentation
"When the type is a collection, this attribute is set to the type
that the collection is expected to contain")
(fully-qualified nil
:type bool
:documentation
"Whether or not the type name is fully qualified"))
(defmacro phpinspect--make-type (&rest property-list)
`(phpinspect--make-type-generated
,@(phpinspect--wrap-plist-name-in-symbol property-list)))
(defun phpinspect--make-types (type-names)
(mapcar (lambda (name) (phpinspect--make-type :name name))
type-names))
(defconst phpinspect-native-typenames
;; self, parent and resource are not valid type name.
;; see https://www.php.net/manual/ja/language.types.declarations.php
;;;
;; However, this list does not need to be valid, it just needs to contain the
;; list of type names that we should not attempst to resolve relatively.
'("array" "bool" "callable" "float" "int" "iterable" "mixed" "object" "string" "void" "self" "static" "this"))
(defvar phpinspect-native-types
(phpinspect--make-types (mapcar (lambda (name) (concat "\\" name))
phpinspect-native-typenames)))
(defvar phpinspect-collection-types
(phpinspect--make-types '("\\array" "\\iterable" "\\SplObjectCollection" "\\mixed"))
"FQNs of types that should be treated as collecitons when inferring types.")
(defvar phpinspect--object-type (phpinspect--make-type :name "\\object" :fully-qualified t))
(defvar phpinspect--static-type (phpinspect--make-type :name "\\static" :fully-qualified t))
(defvar phpinspect--self-type (phpinspect--make-type :name "\\self" :fully-qualified t))
(defvar phpinspect--this-type (phpinspect--make-type :name "\\this" :fully-qualified t))
(defvar phpinspect--null-type (phpinspect--make-type :name "\\null" :fully-qualified t))
(defun phpinspect-define-standard-types ()
(setq phpinspect-native-types
(phpinspect--make-types (mapcar (lambda (name) (concat "\\" name))
phpinspect-native-typenames))
phpinspect-collection-types (phpinspect--make-types
'("\\array" "\\iterable" "\\SplObjectCollection" "\\mixed"))
phpinspect--object-type (phpinspect--make-type :name "\\object" :fully-qualified t)
phpinspect--static-type (phpinspect--make-type :name "\\static" :fully-qualified t)
phpinspect--self-type (phpinspect--make-type :name "\\self" :fully-qualified t)
phpinspect--this-type (phpinspect--make-type :name "\\this" :fully-qualified t)
phpinspect--null-type (phpinspect--make-type :name "\\null" :fully-qualified t)))
(cl-defmethod phpinspect--type-set-name ((type phpinspect--type) (name string))
(setf (phpinspect--type-name-symbol type) (phpinspect-intern-name name)))
(cl-defmethod phpinspect--type-does-late-static-binding ((type phpinspect--type))
"Whether or not TYPE is used for late static binding.
See https://wiki.php.net/rfc/static_return_type ."
(or (phpinspect--type= type phpinspect--static-type)
(phpinspect--type= type phpinspect--this-type)))
(cl-defmethod phpinspect--resolve-late-static-binding
((type phpinspect--type)
(class-type phpinspect--type))
(if (phpinspect--type-does-late-static-binding type)
class-type
type))
(defsubst phpinspect--type-is-native (type)
(catch 'found
(dolist (native phpinspect-native-types)
(when (phpinspect--type= type native)
(throw 'found t)))))
(defsubst phpinspect--type-is-collection (type)
(catch 'found
(dolist (collection phpinspect-collection-types)
(when (phpinspect--type= type collection)
(throw 'found t)))))
(cl-defmethod phpinspect--type-name ((type phpinspect--type))
(phpinspect-name-string (phpinspect--type-name-symbol type)))
(defun phpinspect--get-bare-class-name-from-fqn (fqn)
(car (last (split-string fqn "\\\\"))))
(cl-defmethod phpinspect--type-bare-name ((type phpinspect--type))
"Return just the name, without namespace part, of TYPE."
(phpinspect--get-bare-class-name-from-fqn (phpinspect--type-name type)))
(cl-defmethod phpinspect--type= ((type1 phpinspect--type) (type2 phpinspect--type))
(eq (phpinspect--type-name-symbol type1) (phpinspect--type-name-symbol type2)))
(defun phpinspect--resolve-type-name (types namespace type)
"Get the FQN for TYPE, using TYPES and NAMESPACE as context.
TYPES must be an alist with class names as cars and FQNs as cdrs.
NAMESPACE may be nil, or a string with a namespace FQN."
(phpinspect--log "Resolving %s from namespace %s" type namespace)
;; Absolute FQN
(cond ((string-match "^\\\\" type)
type)
;; Native type
((member type phpinspect-native-typenames)
(concat "\\" type))
;; Relative FQN
((and namespace (string-match "\\\\" type))
(concat "\\" namespace "\\" type))
;; Clas|interface|trait name
(t (let ((from-types (assoc-default (phpinspect-intern-name type) types #'eq)))
(cond (from-types
(phpinspect--type-name from-types))
(namespace
(concat "\\" namespace "\\" type))
(t (concat "\\" type)))))))
(cl-defmethod phpinspect--type-resolve (types namespace (type phpinspect--type))
(unless (phpinspect--type-fully-qualified type)
(phpinspect--type-set-name
type
(phpinspect--resolve-type-name types namespace (phpinspect--type-name type)))
(setf (phpinspect--type-fully-qualified type) t))
(when (phpinspect--type-is-collection type)
(setf (phpinspect--type-collection type) t))
type)
(defun phpinspect--find-innermost-incomplete-class (token)
(let ((last-token (car (last token))))
(cond ((phpinspect-incomplete-class-p token) token)
((phpinspect-incomplete-token-p last-token)
(phpinspect--find-innermost-incomplete-class last-token)))))
(defun phpinspect--find-class-token (token)
"Recurse into token tree until a class is found."
(when (and (listp token) (> (length token) 1))
(let ((last-token (car (last token))))
(cond ((phpinspect-class-p token) token)
(last-token
(phpinspect--find-class-token last-token))))))
(defun phpinspect--make-type-resolver (types &optional token-tree namespace)
"Little wrapper closure to pass around and resolve types with."
(let* ((inside-class
(and token-tree (or (phpinspect--find-innermost-incomplete-class token-tree)
(phpinspect--find-class-token token-tree))))
(inside-class-name
(and inside-class (phpinspect--get-class-name-from-token inside-class))))
(lambda (type)
(phpinspect--type-resolve
types
namespace
(if (and inside-class-name (phpinspect--type= type phpinspect--self-type))
(progn
(phpinspect--log "Returning inside class name for %s : %s"
type inside-class-name)
(phpinspect--make-type :name inside-class-name))
;; else
type)))))
(cl-defgeneric phpinspect--format-type-name (name)
(if name
(error "Unexpected value: %s" name)
"unknown-type"))
(cl-defmethod phpinspect--format-type-name ((name string))
(string-remove-prefix "\\" name))
(cl-defmethod phpinspect--format-type-name ((type phpinspect--type))
(phpinspect--format-type-name (phpinspect--type-name type)))
(cl-defstruct (phpinspect--function (:constructor phpinspect--make-function-generated)
(:copier phpinspect--copy-function))
"A PHP function."
(name-symbol nil
:type symbol
:documentation
"A symbol associated with the name of the function")
(token nil
:type phpinspect-function-p
:documentation
"The tokens with which this function was declared.")
(-inherited nil
:type boolean
:documentation
"Whether this function has been incorporated into a class as
method of an extended class.")
(scope nil
:type phpinspect-scope
:documentation
"When the function is a method, this should contain the
scope of the function as returned by `phpinspect-parse-scope`.")
(arguments nil
:type list
:documentation
"A simple list with function arguments and their
types in tuples. Each list should have the name of the variable
as first element and the type as second element.")
(return-type nil
:type phpinspect--type
:documentation
"A phpinspect--type object representing the the
return type of the function."))
(defmacro phpinspect--make-function (&rest property-list)
`(phpinspect--make-function-generated
,@(phpinspect--wrap-plist-name-in-symbol property-list)))
(cl-defmethod phpinspect--function-set-name ((func phpinspect--function) (name string))
(setf (phpinspect--function-name-symbol func) (intern name phpinspect-names)))
(define-inline phpinspect--function-name (func)
(inline-quote (phpinspect-name-string (phpinspect--function-name-symbol ,func))))
(cl-defstruct (phpinspect--variable (:constructor phpinspect--make-variable))
"A PHP Variable."
(name nil
:type string
:documentation
"A string containing the name of the variable.")
(scope nil
:documentation
"When the variable is an object attribute, this should
contain the scope of the variable as returned by
`phpinspect-parse-scope'")
(lifetime nil
:documentation
"The lifetime of the variable (e.g. whether it is static or not). Will
contain the parsed keyword token indicating the lifetime of the variable")
(mutability nil
:documentation
"The mutability of the variable (e.g. whether it is constant or
not). Will contain the parsed keyword token indicating the
mutability of the variable")
(type nil
:type string
:documentation
"A string containing the FQN of the variable's type"))
(defun phpinspect--variable-static-p (variable)
(phpinspect-static-p (phpinspect--variable-lifetime variable)))
(defun phpinspect--variable-const-p (variable)
(phpinspect-const-p (phpinspect--variable-mutability variable)))
(defun phpinspect--variable-vanilla-p (variable)
(not (or (phpinspect--variable-static-p variable)
(phpinspect--variable-const-p variable))))
(defun phpinspect--use-to-type (use)
(let* ((fqn (cadr (cadr use)))
(type (phpinspect--make-type :name (if (string-match "^\\\\" fqn)
fqn
(concat "\\" fqn))
:fully-qualified t))
(type-name (if (and (phpinspect-word-p (caddr use))
(string= "as" (cadr (caddr use))))
(cadr (cadddr use))
(progn (string-match "[^\\]+$" fqn)
(match-string 0 fqn)))))
(cons (phpinspect-intern-name type-name) type)))
(defun phpinspect--uses-to-types (uses)
(mapcar #'phpinspect--use-to-type uses))
(defun phpinspect--get-class-name-from-token (class-token)
(let ((subtoken (seq-find (lambda (word)
(and (phpinspect-word-p word)
(not (string-match
(concat "^" (phpinspect--class-keyword-handler-regexp))
(concat (cadr word) " ")))))
(cadr class-token))))
(cadr subtoken)))
(defun phpinspect--index-class-declaration (decl type-resolver)
;; Find out what the class extends or implements
(let (encountered-extends encountered-implements encountered-class
class-name extends implements used-types)
(dolist (word decl)
(if (phpinspect-word-p word)
(cond ((string= (cadr word) "extends")
(phpinspect--log "Class %s extends other classes" class-name)
(setq encountered-extends t))
((string= (cadr word) "implements")
(setq encountered-extends nil)
(phpinspect--log "Class %s implements in interface" class-name)
(setq encountered-implements t))
((string-match-p
(eval-when-compile
(concat "^" (phpinspect--class-keyword-handler-regexp) "?$"))
(cadr word))
(setq encountered-class t))
(t
(phpinspect--log "Calling Resolver from index-class on %s" (cadr word))
(cond (encountered-extends
(push (funcall type-resolver (phpinspect--make-type
:name (cadr word)))
extends)
(push (cadr word) used-types))
(encountered-implements
(push (funcall type-resolver (phpinspect--make-type
:name (cadr word)))
implements)
(push (cadr word) used-types))
(encountered-class
(setq class-name (funcall type-resolver (phpinspect--make-type :name (cadr word)))
encountered-class nil)))))))
(list class-name extends implements used-types)))
(defun phpinspect-namespace-name (namespace)
(or (and (phpinspect-namespace-p namespace)
(phpinspect-word-p (cadr namespace))
(cadadr namespace))
""))
(provide 'phpinspect-type)
;;; phpinspect-type.el ends here

@ -1,306 +0,0 @@
;;; phpinspect-util.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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:
(defvar phpinspect-names (make-hash-table :test #'equal :size 5000 :rehash-size 1.2)
"An hash-table containing cons cells representing encountered names in
PHP code. Used to optimize string comparison. See also `phpinspect-intern-name'")
(defun phpinspect-make-name-hash ()
(make-hash-table :test #'equal :size 5000 :rehash-size 1.2))
(define-inline phpinspect-name-string (name)
(inline-quote (cdr ,name)))
(defvar phpinspect-project-root-file-list
'("composer.json" "composer.lock" ".git" ".svn" ".hg")
"List of files that could indicate a project root directory.")
(defvar phpinspect--debug nil
"Enable debug logs for phpinspect by setting this variable to true")
(defun phpinspect-message (&rest args)
(let ((format-string (car args))
(args (cdr args)))
(apply #'message `(,(concat "[phpinspect] " format-string) ,@args))))
(defun phpinspect-toggle-logging ()
(interactive)
(if (setq phpinspect--debug (not phpinspect--debug))
(phpinspect-message "Enabled phpinspect logging.")
(phpinspect-message "Disabled phpinspect logging.")))
(eval-and-compile
(defvar phpinspect-log-groups nil)
(defvar phpinspect-enabled-log-groups nil)
(defvar-local phpinspect--current-log-group nil))
(define-inline phpinspect--declare-log-group (group)
(unless (and (inline-const-p group) (symbolp (inline-const-val group)))
(inline-error "Log group name should be a symbol"))
(inline-quote
(progn
(add-to-list 'phpinspect-log-groups
(cons (macroexp-file-name) ,group)))))
(defun phpinspect-log-group-enabled-p (group)
(seq-find (lambda (cons)
(eq group (cdr cons)))
phpinspect-enabled-log-groups))
(defmacro phpinspect--log (&rest args)
(let ((log-group (alist-get (macroexp-file-name)
phpinspect-log-groups nil nil #'string=)))
`(when (and phpinspect--debug
(or (not phpinspect-enabled-log-groups)
,(when log-group
`(member (quote ,log-group) phpinspect-enabled-log-groups))))
(with-current-buffer (get-buffer-create "**phpinspect-logs**")
(unless window-point-insertion-type
(set (make-local-variable 'window-point-insertion-type) t))
(goto-char (buffer-end 1))
(insert (concat "[" (format-time-string "%H:%M:%S") "]: "
,(if log-group (concat "(" (symbol-name log-group) ") ") "")
(format ,@args) "\n"))))))
(defun phpinspect-filter-logs (group-name)
(interactive (list (completing-read "Log group: "
(mapcar (lambda (g) (symbol-name (cdr g)))
phpinspect-log-groups)
nil t)))
(add-to-list 'phpinspect-enabled-log-groups (intern group-name)))
(defun phpinspect-unfilter-logs ()
(interactive)
(setq phpinspect-enabled-log-groups nil))
(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))))))))
(defun phpinspect-intern-name (name)
(or (gethash name phpinspect-names)
(puthash name (cons 'phpinspect-name name) phpinspect-names)))
(defun phpinspect-names-to-alist (names)
(let ((alist))
(dolist (name names)
(push (cons (phpinspect-name-string name) name) alist))
alist))
(defsubst phpinspect--wrap-plist-name-in-symbol (property-list)
(let ((new-plist)
(wrap-value))
(dolist (item property-list)
(when wrap-value
(setq item `(phpinspect-intern-name ,item))
(setq wrap-value nil))
(when (eq item :name)
(setq item :name-symbol)
(setq wrap-value t))
(push item new-plist))
(nreverse new-plist)))
(cl-defstruct (phpinspect--pattern
(:constructor phpinspect--make-pattern-generated))
"An object that can be used to match lists to a given
pattern. See `phpinspect--match-sequence'."
(matcher nil
:type lambda
:documentation "The function used to match sequences")
(code nil
:type list
:documentation "The original code list used to create this pattern"))
(defmacro phpinspect--make-pattern (&rest pattern)
`(phpinspect--make-pattern-generated
:matcher (phpinspect--match-sequence-lambda ,@pattern)
:code (list ,@(mapcar (lambda (part) (if (eq '* part) `(quote ,part) part))
pattern))))
(defmacro phpinspect--match-sequence-lambda (&rest pattern)
(let ((sequence-sym (gensym)))
`(lambda (,sequence-sym)
(phpinspect--match-sequence ,sequence-sym ,@pattern))))
(cl-defmethod phpinspect--pattern-match ((pattern phpinspect--pattern) sequence)
"Match SEQUENCE to PATTERN."
(funcall (phpinspect--pattern-matcher pattern) sequence))
(defmacro phpinspect--match-sequence (sequence &rest pattern)
"Match SEQUENCE to positional matchers defined in PATTERN.
PATTERN is a plist with the allowed keys being :m and :f. Each
key-value pair in the plist defines a match operation that is
applied to the corresponding index of SEQUENCE (so for ex.: key 0
is applied to pos. 0 of SEQUENCE, key 1 to pos. 1, and so on).
Possible match operations:
:m - This key can be used to match a list element to the literal
value supplied for it, using the `equal' comparison function. For
example, providing `(\"foobar\") as value will result in the
comparison (equal (elt SEQUENCE pos) `(\"foobar\")). There is one
exception to this rule: using the symbol * as value for the :m
key will match anything, essentially skipping comparison for the
element at this position in SEQUENCE.
:f - This key can be used to match a list element by executing
the function provided as value. The function is executed with the
list element as argument, and will be considered as matching if
it evaluates to a non-nil value."
(declare (indent 1))
(let* ((pattern-length (length pattern))
(sequence-length (/ pattern-length 2))
(sequence-pos 0)
(sequence-sym (gensym))
(match-sym (gensym))
(match-rear-sym (gensym))
(checkers (cons nil nil))
(checkers-rear checkers)
key value)
(while (setq key (pop pattern))
(unless (keywordp key)
(error "Invalid pattern argument, expected keyword, got: %s" key))
(unless (setq value (pop pattern))
(error "No value for key %s" key))
(cond ((eq key :m)
(unless (eq value '*)
(setq checkers-rear
(setcdr checkers-rear
(cons `(equal ,value (elt ,sequence-sym ,sequence-pos)) nil)))))
((eq key :f)
(setq checkers-rear
(setcdr
checkers-rear
(cons
(if (symbolp value)
`(,value (elt ,sequence-sym ,sequence-pos))
`(funcall ,value (elt ,sequence-sym ,sequence-pos)))
nil))))
(t (error "Invalid keyword: %s" key)))
(setq checkers-rear
(setcdr checkers-rear
(cons `(setq ,match-rear-sym
(setcdr ,match-rear-sym
(cons (elt ,sequence-sym ,sequence-pos) nil)))
nil)))
(setq sequence-pos (+ sequence-pos 1)))
(setq checkers (cdr checkers))
`(let* ((,sequence-sym ,sequence)
(,match-sym (cons nil nil))
(,match-rear-sym ,match-sym))
(and (= ,sequence-length (length ,sequence))
,@checkers
(cdr ,match-sym)))))
(defun phpinspect--pattern-concat (pattern1 pattern2)
(let* ((pattern1-sequence-length (/ (length (phpinspect--pattern-code pattern1)) 2)))
(phpinspect--make-pattern-generated
:matcher (lambda (sequence)
(unless (< (length sequence) pattern1-sequence-length)
(and (phpinspect--pattern-match
pattern1
(butlast sequence (- (length sequence) pattern1-sequence-length)))
(phpinspect--pattern-match
pattern2
(last sequence (- (length sequence) pattern1-sequence-length))))))
: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))
(defun phpinspect--determine-completion-point ()
"Find first point backwards that could contain any kind of
context for completion."
(save-excursion
(re-search-backward "[^[:blank:]\n]" nil t)
(forward-char)
(point)))
(defmacro phpinspect-json-preset (&rest body)
"Default options to wrap around `json-read' and similar BODY."
`(let ((json-object-type 'hash-table)
(json-array-type 'list)
(json-key-type 'string))
,@body))
(defun phpinspect--input-pending-p (&optional check-timers)
(unless noninteractive
(input-pending-p check-timers)))
(defun phpinspect-thread-pause (pause-time mx continue)
"Pause current thread using MX and CONTINUE for PAUSE-TIME idle seconds.
PAUSE-TIME must be the idle time that the thread should pause for.
MX must be a mutex
CONTINUE must be a condition-variable"
(phpinspect--log "Thread '%s' is paused for %d seconds" (thread-name (current-thread)) pause-time)
(run-with-idle-timer
pause-time
nil
(lambda () (with-mutex mx (condition-notify continue))))
(with-mutex mx (condition-wait continue))
(phpinspect--log "Thread '%s' continuing execution" (thread-name (current-thread))))
(provide 'phpinspect-util)
;;; phpinspect-util.el ends here

@ -1,250 +0,0 @@
;; phpinspect-worker.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 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 'cl-lib)
(require 'phpinspect-util)
(require 'phpinspect-project-struct)
(require 'phpinspect-index)
(require 'phpinspect-class)
(require 'phpinspect-queue)
(require 'phpinspect-pipeline)
(eval-when-compile
(phpinspect--declare-log-group 'worker))
(defcustom phpinspect-worker-pause-time 1
"Number of seconds that `phpinspect-worker' should pause when
user input is detected. A higher value means better
responsiveness, at the cost of slower code indexation. On modern
hardware this probably doesn't need to be tweaked."
:type 'number
:group 'phpinspect)
(defvar phpinspect-worker nil
"Contains the phpinspect worker that is used by all projects.")
(cl-defstruct (phpinspect-worker
(:constructor phpinspect-make-worker-generated))
(queue nil
:type phpinspect-queue-item
:documentation
"The queue of tasks that are pending")
(thread nil
:type thread
:documentation
"The thread of this worker")
(continue-running nil
:type bool
:documentation
"Whether or not the thread should continue
running. If this is nil, the thread is stopped.")
(skip-next-pause nil
:type bool
:documentation
"Whether or not the thread should skip its next scheduled pause."))
(cl-defstruct (phpinspect-dynamic-worker
(:constructor phpinspect-make-dynamic-worker-generated))
"A dynamic worker is nothing other than an object that is
supported by all of the same methods as a `phpinspect-worker`,
but relies on an underlying, global worker to actually do the
work. The reason for its implementation is to allow users to
manage phpinspect's worker thread centrally in a dynamic
variable, while also making the behaviour of objects that depend
on the worker independent of dynamic variables during testing.")
(cl-defmethod phpinspect-resolve-dynamic-worker ((_worker phpinspect-dynamic-worker))
phpinspect-worker)
(defun phpinspect-make-dynamic-worker ()
(phpinspect-make-dynamic-worker-generated))
(defsubst phpinspect-make-worker ()
"Create a new worker object."
(let ((worker (phpinspect-make-worker-generated)))
(setf (phpinspect-worker-queue worker)
(phpinspect-make-queue (phpinspect-worker-make-wakeup-function worker)))
worker))
(define-error 'phpinspect-wakeup-thread
"This error is used to wakeup the index thread")
(cl-defgeneric phpinspect-worker-make-wakeup-function (worker)
"Create a function that can be used to wake up WORKER's thread.")
(cl-defmethod phpinspect-worker-wakeup ((worker phpinspect-worker))
(when (eq main-thread (thread--blocker (phpinspect-worker-thread worker)))
(phpinspect--log "Attempting to wakeup worker thread")
(thread-signal (phpinspect-worker-thread worker)
'phpinspect-wakeup-thread nil)))
(cl-defmethod phpinspect-worker-make-wakeup-function ((worker phpinspect-worker))
(lambda ()
(phpinspect-worker-wakeup worker)))
(cl-defmethod phpinspect-worker-make-wakeup-function ((worker phpinspect-dynamic-worker))
(phpinspect-worker-make-wakeup-function (phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-live-p (worker)
"Just a shorthand to check whether or not the WORKER's thread is running.")
(cl-defmethod phpinspect-worker-live-p ((worker phpinspect-worker))
(when (phpinspect-worker-thread worker)
(thread-live-p (phpinspect-worker-thread worker))))
(cl-defmethod phpinspect-worker-live-p ((worker phpinspect-dynamic-worker))
(phpinspect-worker-live-p (phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-enqueue (worker task)
"Enqueue a TASK to be executed by WORKER.")
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker) task)
"Specialized enqueuement method for index tasks. Prevents
indexation tasks from being added when there are identical tasks
already present in the queue."
(phpinspect--log "Enqueuing task")
(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)
task))
(cl-defgeneric phpinspect-worker-make-thread-function (worker)
"Create a function that can be used to start WORKER's thread.")
(defun phpinspect--worker-pause ()
(let* ((mx (make-mutex))
(continue (make-condition-variable mx)))
(phpinspect-thread-pause phpinspect-worker-pause-time mx continue)))
(cl-defmethod phpinspect-worker-make-thread-function ((worker phpinspect-worker))
(lambda ()
(while (phpinspect-worker-continue-running worker)
;; This error is used to wake up the thread when new tasks are added to the
;; queue.
(condition-case err
(progn
(phpinspect--log "Dequeueing next task")
(ignore-error phpinspect-wakeup-thread
;; Prevent quitting during tasks, as this can break data integrity
(let* ((inhibit-quit t)
(task (phpinspect-queue-dequeue (phpinspect-worker-queue worker))))
(if task
;; Execute task if it belongs to a project that has not been
;; purged (meaning that it is still actively used).
(if (phpinspect-project-purged (phpinspect-task-project task))
(phpinspect--log "Projecthas been purged. Skipping task")
(phpinspect--log "Executing task")
(phpinspect-task-execute task worker))
;; else: join with the main thread until wakeup is signaled
(phpinspect--log "No tasks, joining main thread")
(thread-join main-thread))))
;; Pause for a second after indexing something, to allow user input to
;; interrupt the thread.
(unless (or (not (phpinspect--input-pending-p))
(phpinspect-worker-skip-next-pause worker))
(phpinspect--worker-pause))
(setf (phpinspect-worker-skip-next-pause worker) nil))
(quit (ignore-error phpinspect-wakeup-thread
(phpinspect--worker-pause)))
(phpinspect-wakeup-thread)
((debug error) (thread-signal main-thread 'phpinspect-worker-error err))
(t (phpinspect--log "Phpinspect worker thread errored :%s" err))))
(phpinspect--log "Worker thread exiting")
(phpinspect-message "phpinspect worker thread exited")))
(cl-defmethod phpinspect-worker-make-thread-function ((worker phpinspect-dynamic-worker))
(phpinspect-worker-make-thread-function
(phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-start (worker)
"Start WORKER's thread.")
(cl-defmethod phpinspect-worker-start ((worker phpinspect-worker))
(if (phpinspect-worker-live-p worker)
(error "Attempt to start a worker that is already running")
(progn
(setf (phpinspect-worker-continue-running worker) t)
(setf (phpinspect-worker-thread worker)
;; Use with-temp-buffer so as to not associate thread with the
;; current buffer. Otherwise, the buffer associated with this thread
;; will be unkillable while the thread is running.
(with-temp-buffer
(make-thread (phpinspect-worker-make-thread-function worker) "phpinspect-worker"))))))
(cl-defmethod phpinspect-worker-start ((worker phpinspect-dynamic-worker))
(phpinspect-worker-start (phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-stop (worker)
"Stop the worker")
(cl-defmethod phpinspect-worker-stop ((worker phpinspect-worker))
(setf (phpinspect-worker-continue-running worker) nil)
(phpinspect-worker-wakeup worker))
(cl-defmethod phpinspect-worker-stop ((worker phpinspect-dynamic-worker))
(phpinspect-worker-stop (phpinspect-resolve-dynamic-worker worker)))
(defun phpinspect-ensure-worker ()
(interactive)
(when (not phpinspect-worker)
(setq phpinspect-worker (phpinspect-make-worker)))
(when (not (phpinspect-worker-live-p phpinspect-worker))
(phpinspect-worker-start phpinspect-worker)))
(defun phpinspect-stop-worker ()
(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.")
(cl-defmethod phpinspect-worker-enqueue ((_worker (eql 'nil-worker)) &rest _ignored))
(cl-defmethod phpinspect-worker-live-p ((_worker (eql 'nil-worker)) &rest _ignored) t)
(provide 'phpinspect-worker)
;;; phpinspect-worker.el ends here

File diff suppressed because it is too large Load Diff

@ -1,101 +0,0 @@
<?php
declare(strict_types=1);
function generateClassStub(\ReflectionClass $class): string
{
$stub = '';
$hasNamespace = '' !== $class->getNamespaceName();
if ($hasNamespace) {
$stub = 'namespace ' . $class->getNamespaceName() . ' {' . PHP_EOL;
}
if ($class->isFinal()) {
$stub .= 'final ';
}
if ($class->isAbstract() && !$class->isInterface()) {
$stub .= 'abstract ';
}
if ($class->isInterface()) {
$stub .= 'interface ';
} else if ($class->isTrait()) {
$stub .= 'trait ';
} else {
$stub .= 'class ';
}
$stub .= $class->getShortName() . ' {' . PHP_EOL;
foreach ($class->getMethods() as $method) {
if ($method->isFinal()) {
$stub .= 'final ';
}
if ($method->isPublic()) {
$stub .= 'public ';
} else if ($method->isPrivate()) {
$stub .= 'private ';
} else if ($method->isProtected()) {
$stub .= 'protected ';
}
if ($method->isStatic()) {
$stub .= 'static ';
}
$stub .= generateFunctionStub($method);
}
$stub .= '}' . PHP_EOL;
if ($hasNamespace) {
$stub .= '}' . PHP_EOL; // Close namespace block
}
return $stub;
}
function generateFunctionStub(\ReflectionFunctionAbstract $function): string
{
$stub = 'function ' . $function->getName() . '(';
$parameters = [];
foreach ($function->getParameters() as $ref_parameter) {
$parameter = '';
if ($ref_parameter->hasType()) {
$parameter .= $ref_parameter->getType()->__toString() . ' ';
}
$parameter .= '$' . $ref_parameter->getName();
if ($ref_parameter->isDefaultValueAvailable()) {
$parameter .= ' = ' . var_export($ref_parameter->getDefaultValue(), true);
}
$parameters[] = $parameter;
}
$stub .= implode(', ', $parameters);
$stub .= ')';
if ($function->hasReturnType()) {
$stub .= ': ' . $function->getReturnType()->__toString();
} else if ($function->hasTentativeReturnType()) {
$stub .= ': ?' . $function->getTentativeReturnType();
}
$stub .= ' {}' . PHP_EOL;
return $stub;
}
foreach (get_defined_functions(false)['internal'] as $function_name) {
echo generateFunctionStub(new ReflectionFunction($function_name));
}
foreach ([...get_declared_classes(), ...get_declared_interfaces(), ...get_declared_traits()] as $class_name) {
echo generateClassStub(new ReflectionClass($class_name));
}

@ -1,35 +0,0 @@
;;; install-deps.el --- Install package dependencies -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: script
;; 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 'lisp-mnt)
(let* ((project-dir (file-name-parent-directory (file-name-directory (macroexp-file-name))))
(file (expand-file-name "phpinspect.el" project-dir))
dependencies)
(with-temp-buffer
(insert-file-contents file)
(setq dependencies (read (lm-header-multiline "package-requires")))
(dolist (dep dependencies)
(package-install (car dep)))))

@ -1,20 +0,0 @@
#!/bin/bash
rm ./**/*.elc
rm *.elc
for file in ./*.el; do
echo 'Compiling '"$file"' ...'
cask emacs -batch -L . --eval '(progn '"(require 'comp)"' (setq byte-compile-error-on-warn t native-compile-target-directory (car native-comp-eln-load-path)) (nreverse native-comp-eln-load-path))' -f batch-byte+native-compile "$file" || break
done
for file in ./**/*.el; do
echo 'Compiling '"$file"' ...'
cask emacs -batch -L . --eval '(progn '"(require 'comp)"' (setq byte-compile-error-on-warn t native-compile-target-directory (car native-comp-eln-load-path)) (nreverse native-comp-eln-load-path))' -f batch-byte+native-compile "$file" || break
done
if [[ -z $NO_REMOVE_ELC ]]; then
rm ./**/*.elc
rm *.elc
fi

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Controller") (:terminator ";") (: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 (:class-variable "repo") (:terminator ";")) (:private (:class-variable "user_repo") (:terminator ";")) (:private (:class-variable "twig") (:terminator ";")) (:private (:class-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") (:terminator ";") (: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)))))))))

@ -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 ";")) (: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 (:class-variable "repo") (:terminator ";")) (:private (:class-variable "user_repo") (:terminator ";")) (:private (:class-variable "twig") (:terminator ";")) (:private (:class-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 ";")) (: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

File diff suppressed because one or more lines are too long

@ -1 +1 @@
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use (:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) (:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") (:word "AuthToken")) (:block (:private (:class-variable "token") (:terminator ";")) (:doc-block (:var-annotation (:word "App\\\\Entity\\\\User"))) (:private (:class-variable "user") (:terminator ";")) (:doc-block (:var-annotation (:word "bool"))) (:private (:class-variable "valid") (:terminator ";")) (:doc-block (:var-annotation (:word "\\DateTime"))) (:private (:class-variable "creation_time") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "string") (:variable "token") (:comma ",") (:word "User") (:variable "user") (:comma ",") (:word "bool") (:variable "valid") (:assignment "=") (:word "false") (:comma ",") (:word "\\DateTime") (:variable "creation_time") (:assignment "=") (:word "null"))) (:block (:variable "this") (:object-attrib (:word "token")) (:assignment "=") (:variable "token") (:terminator ";") (:variable "this") (:object-attrib (:word "user")) (:assignment "=") (:variable "user") (:terminator ";") (:variable "this") (:object-attrib (:word "valid")) (:assignment "=") (:variable "valid") (:terminator ";") (:variable "this") (:object-attrib (:word "creation_time")) (:assignment "=") (:variable "creation_time") (:word "new") (:word "\\DateTime") (:list) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getToken") (:list) (:word "string")) (:block (:word "return") (:variable "this") (:object-attrib (:word "token")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getUser") (:list) (:word "User")) (:block (:word "return") (:variable "this") (:object-attrib (:word "user")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "hasStudentRole") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "role_student")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "isValid") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "valid")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getCreationTime") (:list) (:word "\\DateTime")) (:block (:word "return") (:variable "this") (:object-attrib (:word "creation_time")) (:terminator ";")))) (:doc-block (:return-annotation (:word "DateTime[]"))) (:public (:function (:declaration (:word "function") (:word "arrayReturn") (:list) (:word "array")) (:block (:word "return") (:array (:word "new") (:word "\\DateTime") (:list)) (:terminator ";"))))))))
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use (:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) (:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") (:word "AuthToken")) (:block (:private (:variable "token") (:terminator ";")) (:doc-block (:var-annotation (:word "App\\\\Entity\\\\User"))) (:private (:variable "user") (:terminator ";")) (:doc-block (:var-annotation (:word "bool"))) (:private (:variable "valid") (:terminator ";")) (:doc-block (:var-annotation (:word "\\DateTime"))) (:private (:variable "creation_time") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "string") (:variable "token") (:comma ",") (:word "User") (:variable "user") (:comma ",") (:word "bool") (:variable "valid") (:assignment "=") (:word "false") (:comma ",") (:word "\\DateTime") (:variable "creation_time") (:assignment "=") (:word "null"))) (:block (:variable "this") (:object-attrib (:word "token")) (:assignment "=") (:variable "token") (:terminator ";") (:variable "this") (:object-attrib (:word "user")) (:assignment "=") (:variable "user") (:terminator ";") (:variable "this") (:object-attrib (:word "valid")) (:assignment "=") (:variable "valid") (:terminator ";") (:variable "this") (:object-attrib (:word "creation_time")) (:assignment "=") (:variable "creation_time") (:word "new") (:word "\\DateTime") (:list) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getToken") (:list) (:word "string")) (:block (:word "return") (:variable "this") (:object-attrib (:word "token")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getUser") (:list) (:word "User")) (:block (:word "return") (:variable "this") (:object-attrib (:word "user")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "hasStudentRole") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "role_student")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "isValid") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "valid")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getCreationTime") (:list) (:word "\\DateTime")) (:block (:word "return") (:variable "this") (:object-attrib (:word "creation_time")) (:terminator ";"))))))))

@ -63,12 +63,4 @@ class AuthToken
{
return $this->creation_time;
}
/**
* @return DateTime[]
*/
public function arrayReturn(): array
{
return [ new \DateTime() ];
}
}

File diff suppressed because one or more lines are too long

@ -1 +1 @@
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use (:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) (:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") (:word "AuthToken")) (:block (:private (:class-variable "token") (:terminator ";")) (:private (:class-variable "extra") (:terminator ";")) (:doc-block (:var-annotation (:word "App\\\\Entity\\\\User"))) (:private (:class-variable "user") (:terminator ";")) (:doc-block (:var-annotation (:word "bool"))) (:private (:class-variable "valid") (:terminator ";")) (:doc-block (:var-annotation (:word "\\DateTime"))) (:private (:class-variable "creation_time") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "string") (:variable "token") (:comma ",") (:word "User") (:variable "user") (:comma ",") (:word "bool") (:variable "valid") (:assignment "=") (:word "false") (:comma ",") (:word "\\DateTime") (:variable "creation_time") (:assignment "=") (:word "null"))) (:block (:variable "this") (:object-attrib (:word "token")) (:assignment "=") (:variable "token") (:terminator ";") (:variable "this") (:object-attrib (:word "user")) (:assignment "=") (:variable "user") (:terminator ";") (:variable "this") (:object-attrib (:word "valid")) (:assignment "=") (:variable "valid") (:terminator ";") (:variable "this") (:object-attrib (:word "creation_time")) (:assignment "=") (:variable "creation_time") (:word "new") (:word "\\DateTime") (:list) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getToken") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "token")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getUser") (:list) (:word "User")) (:block (:word "return") (:variable "this") (:object-attrib (:word "user")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "hasStudentRole") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "role_student")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "isValid") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "valid")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "anAddedFunction") (:list)) (:block (:word "return") (:variable "this") (:object-attrib (:word "extra")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getCreationTime") (:list) (:word "\\DateTime")) (:block (:word "return") (:variable "this") (:object-attrib (:word "creation_time")) (:terminator ";"))))))))
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use (:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) (:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") (:word "AuthToken")) (:block (:private (:variable "token") (:terminator ";")) (:private (:variable "extra") (:terminator ";")) (:doc-block (:var-annotation (:word "App\\\\Entity\\\\User"))) (:private (:variable "user") (:terminator ";")) (:doc-block (:var-annotation (:word "bool"))) (:private (:variable "valid") (:terminator ";")) (:doc-block (:var-annotation (:word "\\DateTime"))) (:private (:variable "creation_time") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "string") (:variable "token") (:comma ",") (:word "User") (:variable "user") (:comma ",") (:word "bool") (:variable "valid") (:assignment "=") (:word "false") (:comma ",") (:word "\\DateTime") (:variable "creation_time") (:assignment "=") (:word "null"))) (:block (:variable "this") (:object-attrib (:word "token")) (:assignment "=") (:variable "token") (:terminator ";") (:variable "this") (:object-attrib (:word "user")) (:assignment "=") (:variable "user") (:terminator ";") (:variable "this") (:object-attrib (:word "valid")) (:assignment "=") (:variable "valid") (:terminator ";") (:variable "this") (:object-attrib (:word "creation_time")) (:assignment "=") (:variable "creation_time") (:word "new") (:word "\\DateTime") (:list) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getToken") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "token")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getUser") (:list) (:word "User")) (:block (:word "return") (:variable "this") (:object-attrib (:word "user")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "hasStudentRole") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "role_student")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "isValid") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "valid")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "anAddedFunction") (:list)) (:block (:word "return") (:variable "this") (:object-attrib (:word "extra")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getCreationTime") (:list) (:word "\\DateTime")) (:block (:word "return") (:variable "this") (:object-attrib (:word "creation_time")) (:terminator ";"))))))))

File diff suppressed because one or more lines are too long

@ -13,15 +13,7 @@ use Twig\Environment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* @method int holdUp(Something $thing)
* gaerfawe awjfawijef;aw';ajef0()Eawea
*
* @method Potato holdUp(OtherThing $thing)
* (afwa fae $eafw)
* @method noReturnType(Thing $thing)
*/
class AddressController
class AddressController
{
const A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE = 'a value';
public const ARRAY_CONSTANT = [

@ -1,36 +0,0 @@
;;; phpinspect-test-env.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
(require 'phpinspect-worker)
(require 'phpinspect-cache)
(require 'phpinspect-parser)
;; Make sure that the worker is running. TODO: fully encapsulate the worker the
;; data types that are used in tests so that we don't depend on some global
;; worker object for tests.
(phpinspect-ensure-worker)
(phpinspect-purge-cache)
(defvar phpinspect-test-directory
(file-name-directory (macroexp-file-name))
"Directory that phpinspect tests reside in.")
(defvar phpinspect-test-php-file-directory
(expand-file-name "fixtures" phpinspect-test-directory)
"Directory with syntax trees of example PHP files.")
(defun phpinspect-test-read-fixture-data (name)
(with-temp-buffer
(insert-file-contents-literally (concat phpinspect-test-php-file-directory "/" name ".eld"))
(read (current-buffer))))
(defun phpinspect-test-read-fixture-serialization (name)
(with-temp-buffer
(insert-file-contents-literally (concat phpinspect-test-php-file-directory "/" name ".eld"))
(eval (read (current-buffer)) t)))
(defun phpinspect-test-parse-fixture-code (name)
(phpinspect-parse-file
(concat phpinspect-test-php-file-directory "/" name ".php")))
(provide 'phpinspect-test-env)

@ -1,6 +1,6 @@
;;; phpinspect-test.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;;; phpinspect-test.el --- Unit tests for phpinslect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -26,199 +26,219 @@
(require 'ert)
(require 'phpinspect)
(require 'phpinspect-test-env
(expand-file-name "phpinspect-test-env.el"
(file-name-directory (macroexp-file-name))))
(ert-deftest phpinspect-get-variable-type-in-block ()
(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))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let ((result (phpinspect-get-variable-type-in-block
context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens 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))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
bmap-result)))))
(ert-deftest phpinspect-get-pattern-type-in-block ()
(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))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let ((result (phpinspect-get-pattern-type-in-block
context (phpinspect--make-pattern :m `(:variable "this")
:m `(:object-attrib (:word "potato")))
(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)))))
(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* ((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))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let* ((function-token (car (phpinspect--resolvecontext-enclosing-tokens context)))
(result1 (phpinspect-get-variable-type-in-block
context "bar"
(phpinspect-function-block function-token)
(phpinspect--make-type-resolver-for-resolvecontext context)
(phpinspect-function-argument-list function-token)))
(result2 (phpinspect-get-variable-type-in-block
context "bark"
(phpinspect-function-block function-token)
(phpinspect--make-type-resolver-for-resolvecontext context)
(phpinspect-function-argument-list function-token))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
result1))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
result2)))))
(ert-deftest phpinspect-get-variable-type-in-block-array-foreach ()
(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))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let* ((function-token (seq-find #'phpinspect-function-p
(phpinspect--resolvecontext-enclosing-tokens context)))
(result (phpinspect-get-variable-type-in-block
context "bar"
(phpinspect-function-block function-token)
(phpinspect--make-type-resolver-for-resolvecontext context)
(phpinspect-function-argument-list function-token))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
result)))))
(ert-deftest phpinspect-get-variable-type-in-block-nested-array ()
(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))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let* ((function-token (seq-find #'phpinspect-function-p
(phpinspect--resolvecontext-enclosing-tokens context)))
(result (phpinspect-get-variable-type-in-block
context "bar"
(phpinspect-function-block function-token)
(phpinspect--make-type-resolver-for-resolvecontext context)
(phpinspect-function-argument-list function-token))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
result)))))
(ert-deftest phpinspect--find-assignments-in-token ()
(let* ((tokens (cadr
(phpinspect-parse-string "{ $foo = ['nr 1']; $bar = $nr2; if (true === ($foo = $nr3)) { $foo = $nr4; $notfoo = $nr5; if ([] === ($foo = [ $nr6 ])){ $foo = [ $nr7 ];}}}")))
(expected '(((:variable "foo")
(:assignment "=")
(:array
(:variable "nr7")))
((:variable "foo")
(:assignment "=")
(:array
(:variable "nr6")))
((:variable "notfoo")
(:assignment "=")
(:variable "nr5"))
((:variable "foo")
(:assignment "=")
(:variable "nr4"))
((:variable "foo")
(:assignment "=")
(:variable "nr3"))
((:variable "bar")
(:assignment "=")
(:variable "nr2"))
((:variable "foo")
(:assignment "=")
(:array
(:string "nr 1")))))
(assignments (phpinspect--find-assignments-in-token tokens)))
(should (equal expected assignments))))
(defvar phpinspect-test-php-file-directory
(concat
(file-name-directory
(or load-file-name
buffer-file-name))
"/fixtures")
"Directory with syntax trees of example PHP files.")
(defun phpinspect-test-read-fixture-data (name)
(with-temp-buffer
(insert-file-contents-literally (concat phpinspect-test-php-file-directory "/" name ".eld"))
(read (current-buffer))))
(defun phpinspect-test-parse-fixture-code (name)
(phpinspect-parse-file
(concat phpinspect-test-php-file-directory "/" name ".php")))
(ert-deftest phpinspect-parse-namespaced-class ()
"Test phpinspect-parse on a namespaced class"
(should
(equal (phpinspect-test-read-fixture-data "NamespacedClass")
(phpinspect-test-parse-fixture-code "NamespacedClass"))))
(ert-deftest phpinspect-parse-block ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Block")
(phpinspect-test-parse-fixture-code "Block"))))
(ert-deftest phpinspect-parse-functions ()
"Test phpinspect-parse for php functions"
(should
(equal (phpinspect-test-read-fixture-data "Functions")
(phpinspect-test-parse-fixture-code "Functions"))))
(ert-deftest phpinspect-parse-namespaced-functions ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "NamespacedFunctions")
(phpinspect-test-parse-fixture-code "NamespacedFunctions"))))
(ert-deftest phpinspect-parse-variable ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Variable")
(phpinspect-test-parse-fixture-code "Variable"))))
(ert-deftest phpinspect-parse-word ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Word")
(phpinspect-test-parse-fixture-code "Word"))))
(ert-deftest phpinspect-parse-array ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Array")
(phpinspect-test-parse-fixture-code "Array"))))
(ert-deftest phpinspect-parse-short-function ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "ShortFunction")
(phpinspect-test-parse-fixture-code "ShortFunction"))))
(ert-deftest phpinspect-parse-two-short-functions ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "TwoShortFunctions")
(phpinspect-test-parse-fixture-code "TwoShortFunctions"))))
(ert-deftest phpinspect-parse-small-namespaced-class ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "SmallNamespacedClass")
(phpinspect-test-parse-fixture-code "SmallNamespacedClass"))))
;; If this test fails, the syntax tree has a breaking change in it. Regenerate the
;; fixtures and fix anything that is broken.
(ert-deftest phpinspect-syntax-tree-change ()
(let ((index (phpinspect--index-tokens
(phpinspect-test-parse-fixture-code "IndexClass1")))
(expected-result (phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1"))))
(should (equal index expected-result))))
(ert-deftest phpinspect-index-tokens ()
(should (equal
(phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1"))
(phpinspect-test-read-fixture-data "IndexClass1-indexed"))))
(ert-deftest phpinspect-merge-class-indexes ()
(should (equal
(phpinspect--merge-indexes
(phpinspect-test-read-fixture-data "IndexClass1-indexed")
(phpinspect-test-read-fixture-data "IndexClass2-indexed"))
(phpinspect-test-read-fixture-data
"class-index-1-2-undestructive-merge"))))
(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 (string= "\\array" (funcall type-resolver "array")))
(should (string= "\\array" (funcall type-resolver "\\array")))
(should (string= "\\Symfony\\Component\\HttpFoundation\\Response"
(funcall type-resolver "Response")))
(should (string= "\\Response" (funcall type-resolver "\\Response")))
(should (string= "\\App\\Controller\\GastonLagaffe"
(funcall type-resolver "GastonLagaffe")))
(should (string= "\\App\\Controller\\Dupuis\\GastonLagaffe"
(funcall type-resolver "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 (string= "\\array" (funcall type-resolver "array")))
(should (string= "\\array" (funcall type-resolver "\\array")))
(should (string= "\\Symfony\\Component\\HttpFoundation\\Response"
(funcall type-resolver "Response")))
(should (string= "\\Response" (funcall type-resolver "\\Response")))
(should (string= "\\App\\Controller\\GastonLagaffe"
(funcall type-resolver "GastonLagaffe")))
(should (string= "\\App\\Controller\\Dupuis\\GastonLagaffe"
(funcall type-resolver "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 (string= "\\array" (funcall type-resolver "array")))
(should (string= "\\array" (funcall type-resolver "\\array")))
(should (string= "\\Symfony\\Component\\HttpFoundation\\Response"
(funcall type-resolver "Response")))
(should (string= "\\Response" (funcall type-resolver "\\Response")))
(should (string= "\\App\\Controller\\GastonLagaffe"
(funcall type-resolver "GastonLagaffe")))
(should (string= "\\App\\Controller\\Dupuis\\GastonLagaffe"
(funcall type-resolver "Dupuis\\GastonLagaffe")))))
(ert-deftest phpinspect-index-static-methods ()
(let* ((class-tokens
`(:root
(:class
(:declaration (:word "class") (:word "Potato"))
(:block
(:static
(:function (:declaration (:word "function")
(:word "staticMethod")
(:list (:variable "untyped")
(:comma)
(:word "array")
(:variable "things")))
(:block)))))))
(index (phpinspect--index-tokens class-tokens))
(expected-index
`(phpinspect--root-index
(classes
("\\Potato" phpinspect--class
(methods)
(class-name . "\\Potato")
(static-methods . (,(phpinspect--make-function
:name "staticMethod"
:scope '(:public)
:arguments '(("untyped" nil)
("things" "\\array"))
:return-type nil)))
(static-variables)
(variables)
(constants)
(extends)
(implements)))
(functions))))
(should (equal expected-index index))))
(ert-deftest phpinspect-resolve-type-from-context ()
(let* ((pctx (phpinspect-make-pctx :incremental t :bmap (phpinspect-make-bmap)))
(code "
(let* ((token-tree (phpinspect-parse-string "
namespace Amazing;
class FluffBall
@ -235,18 +255,8 @@ class FluffBall
$ball = $this->fluffer;
if ($ball) {
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 "
if(isset($ball->fluff()->poof->upFluff->"))
(fluffer "
namespace Amazing;
use Vendor\\FluffLib\\Fluff;
@ -256,8 +266,8 @@ class Fluffer
public function fluff(): Fluff
{
}
}"))
(vendor-fluff (phpinspect-parse-string "
}")
(vendor-fluff "
namespace Vendor\\FluffLib;
class Fluff
{
@ -265,8 +275,8 @@ class Fluff
* @var FlufferUpper
*/
public $poof;
}"))
(vendor-fluffer-upper (phpinspect-parse-string "
}")
(vendor-fluffer-upper "
namespace Vendor\\FluffLib;
class FlufferUpper
{
@ -275,38 +285,89 @@ class FlufferUpper
{
$this->upFluff = $upFluff;
}
}"))
}")
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(context (phpinspect-get-resolvecontext bmap 310)))
(phpinspect-class-filepath-function
(lambda (fqn)
(pcase fqn
("\\Amazing\\Fluffer" "fluffer")
("\\Vendor\\FluffLib\\Fluff" "vendor-fluff")
("\\Vendor\\FluffLib\\FlufferUpper" "vendor-fluffer-upper")
(_ (ert-fail (format "Unexpected class FQN filepath was requested: %s" fqn))))))
(phpinspect-insert-file-contents-function
(lambda (filepath)
(pcase filepath
("fluffer" (insert fluffer))
("vendor-fluff" (insert vendor-fluff))
("vendor-fluffer-upper" (insert vendor-fluffer-upper))
(_ (ert-fail (format "Unexpected filepath was requested: %s" filepath))))))
(context (phpinspect--get-resolvecontext token-tree)))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-project-root)
(cdar (alist-get 'classes (cdr (phpinspect--index-tokens token-tree)))))
(setf (phpinspect--resolvecontext-project-root context)
"phpinspect-test")
(should (equal "\\Vendor\\FluffLib\\DoubleFluffer"
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))))
(ert-deftest phpinspect-eldoc-function-for-object-method ()
(let* ((php-code "
class Thing
{
function getThis(\\DateTime $moment, Thing $thing, $other): static
{
return $this;
}
function doStuff()
{
$this->getThis(")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100))
(phpinspect-purge-cache)
(dolist (tree (list token-tree fluffer vendor-fluff vendor-fluffer-upper))
(phpinspect-cache-project-class
"phpinspect-test"
(cdar (alist-get 'classes (cdr (phpinspect--index-tokens tree))))))
(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
context))))))
(phpinspect-cache-project-class
(phpinspect-project-root)
(cdar (alist-get 'classes (cdr index))))
(should (string= "getThis: ($moment DateTime, $thing Thing, $other): Thing"
(with-temp-buffer
(insert php-code)
(phpinspect-eldoc-function))))))
(ert-deftest phpinspect-eldoc-function-for-static-method ()
(let* ((php-code "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
{
return $this;
}
function doStuff()
{
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))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-project-root)
(cdar (alist-get 'classes (cdr index))))
(should (string= "doThing: ($moment DateTime, $thing Thing, $other): Thing"
(with-temp-buffer
(insert php-code)
(phpinspect-eldoc-function))))))
(ert-deftest phpinspect-resolve-type-from-context-static-method ()
(with-temp-buffer
(insert "
(let* ((php-code "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
@ -317,28 +378,24 @@ class Thing
function doStuff()
{
self::doThing()->")
(let* ((bmap (phpinspect-make-bmap))
(tokens (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap bmap)
(phpinspect-parse-current-buffer)))
(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 bmap (point))))
(context (phpinspect--get-resolvecontext tokens)))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(phpinspect-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 (string= "\\Thing"
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))))
(ert-deftest phpinspect-resolve-type-from-context-static-method-with-preceding-words ()
(with-temp-buffer
(insert "
(let* ((php-code "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
@ -350,27 +407,24 @@ class Thing
{
if (true) {
return self::doThing()->")
(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)))))))
(ert-deftest phpinspect-get-last-statement-in-token-with-static-attribute-context ()
(let* ((php-code-function "
(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-project-root)
(cdar (alist-get 'classes (cdr index))))
(should (string= "\\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 "
function doStuff()
{
return self::doThing()")
@ -405,59 +459,5 @@ class Thing
(phpinspect--get-last-statement-in-token
(phpinspect-parse-string php-code-bare))))))
(ert-deftest phpinspect--find-assignments-by-predicate ()
(let* ((token '(:block
(:variable "bam") (:object-attrib "boom") (:assignment "=")
(:variable "beng") (:terminator)
(:variable "notbam") (:word "nonsense") (:assignment "=") (:string) (:terminator)
(:variable "bam") (:comment) (:object-attrib "boom") (:assignment "=")
(:variable "wat") (:object-attrib "call") (:terminator)))
(result (phpinspect--find-assignments-by-predicate
token
(phpinspect--match-sequence-lambda :m `(:variable "bam") :m `(:object-attrib "boom")))))
(should (= 2 (length result)))
(dolist (assignment result)
(should (equal '((:variable "bam") (:object-attrib "boom"))
(phpinspect--assignment-to assignment))))
(should (equal '((:variable "beng"))
(phpinspect--assignment-from (cadr result))))
(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"))
(load-file (concat phpinspect-test-directory "/test-eldoc.el"))
(load-file (concat phpinspect-test-directory "/test-fs.el"))
(load-file (concat phpinspect-test-directory "/test-project.el"))
(load-file (concat phpinspect-test-directory "/test-buffer.el"))
(load-file (concat phpinspect-test-directory "/test-index.el"))
(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"))
(load-file (concat phpinspect-test-directory "/test-parser.el"))
(load-file (concat phpinspect-test-directory "/test-parse-context.el"))
(load-file (concat phpinspect-test-directory "/test-splayt.el"))
(load-file (concat phpinspect-test-directory "/test-pipeline.el"))
(load-file (concat phpinspect-test-directory "/test-toc.el"))
(load-file (concat phpinspect-test-directory "/test-meta.el"))
(provide 'phpinspect-test)
;;; phpinspect-test.el ends here

@ -1,183 +0,0 @@
; test-autoload.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-fs)
(require 'phpinspect-autoload)
(require 'phpinspect-resolvecontext)
(ert-deftest phpinspect-filename-to-typename ()
(should (eq (phpinspect-intern-name "\\Foo\\Bar") (phpinspect-filename-to-typename "src/" "src/Foo////////Bar.php")))
(should (eq (phpinspect-intern-name "\\Foo\\Bar") (phpinspect-filename-to-typename "src/somewhere/else/" "src/somewhere/else/Foo/Bar.php"))))
(ert-deftest phpinspect-find-composer-json-files ()
(let* ((fs (phpinspect-make-virtual-fs)))
(phpinspect-virtual-fs-set-file fs
"/root/composer.json"
"{ \"autoload\": { \"psr-4\": {\"WoW\\\\Dwarves\\\\\": \"src/\"}}}")
(phpinspect-virtual-fs-set-file fs
"/root/vendor/runescape/client/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(phpinspect-virtual-fs-set-file fs
"/root/vendor/apples/pears/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(let ((sorter (lambda (file1 file2) (string-lessp (cdr file1) (cdr file2)))))
(should (equal (sort (copy-sequence
'((vendor . "/root/vendor/apples/pears/composer.json")
(vendor . "/root/vendor/runescape/client/composer.json")
(local . "/root/composer.json")))
sorter)
(sort (phpinspect-find-composer-json-files fs "/root")
sorter))))))
(ert-deftest phpinspect-autoload-composer-json-iterator ()
(let* ((fs (phpinspect-make-virtual-fs))
(autoloader (phpinspect-make-autoloader
:fs fs
:project-root-resolver (lambda () "/root")
:file-indexer
(phpinspect-project-make-file-indexer
(phpinspect--make-project :root "/root" :fs fs))))
result error)
(phpinspect-virtual-fs-set-file fs
"/root/composer.json"
"{ \"autoload\": { \"psr-4\": {\"WoW\\\\Dwarves\\\\\": \"src/\"}}}")
(phpinspect-virtual-fs-set-file fs
"/root/vendor/runescape/client/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(phpinspect-virtual-fs-set-file fs
"/root/vendor/apples/pears/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]},
\"psr-4\": {\"Another\\\\Namespace\\\\\": [\"separate/\"]}}}")
(phpinspect-pipeline (phpinspect-find-composer-json-files fs "/root")
:async (lambda (res err)
(setq result res
error err))
:into (phpinspect-iterate-composer-jsons :with-context autoloader))
(while (not (or result error))
(thread-yield))
(should-not error)
(should (= 4 (length result)))
(should (= 2 (length (seq-filter #'phpinspect-psr0-p result))))
(should (= 2 (length (seq-filter #'phpinspect-psr4-p result))))))
(ert-deftest phpinspect-al-put-type-bag ()
(let ((al (phpinspect-make-autoloader)))
(phpinspect-autoloader-put-type-bag al (phpinspect-intern-name "\\App\\Place\\Mountain"))
(phpinspect-autoloader-put-type-bag al (phpinspect-intern-name "\\App\\Earth\\Mountain"))
(should (equal `(,(phpinspect-intern-name "\\App\\Place\\Mountain")
,(phpinspect-intern-name "\\App\\Earth\\Mountain"))
(phpinspect-autoloader-get-type-bag al (phpinspect-intern-name "Mountain"))))))
(ert-deftest phpinspect-al-strategy-execute ()
(let* ((fs (phpinspect-make-virtual-fs))
(project (phpinspect--make-project :root "/project/root" :fs fs))
(autoloader (phpinspect-make-autoloader
:fs fs
:project-root-resolver (lambda () "/project/root")
:file-indexer (phpinspect-project-make-file-indexer project)))
result error)
(setf (phpinspect-project-autoload project) autoloader)
(phpinspect-virtual-fs-set-file
fs
"/project/root/composer.json"
"{ \"autoload\": { \"psr-4\": {\"App\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(phpinspect-virtual-fs-set-file fs "/project/root/src/TestClass.php" "")
(phpinspect-virtual-fs-set-file
fs
"/project/root/vendor/runescape/client/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(phpinspect-virtual-fs-set-file
fs "/project/root/vendor/runescape/client/src/TestClass.php" "")
(phpinspect-virtual-fs-set-file
fs
"/project/root/vendor/runescape/client/src/Runescape/Banana/App.php"
"")
(phpinspect-virtual-fs-set-file
fs "/project/root/vendor/runescape/client/src/LibClass.php" "")
(phpinspect-virtual-fs-set-file
fs
"/project/root/vendor/not-runescape/wow/composer.json"
"{ \"autoload\": { \"psr-4\": {\"WoW\\\\Dwarves\\\\\": \"src/\"},
\"files\": [ \"include/FilesList.php\"]}}")
(phpinspect-virtual-fs-set-file fs
"/project/root/vendor/not-runescape/wow/include/FilesList.php"
"<?php class FilesList { function list() {} }")
(phpinspect-virtual-fs-set-file
fs "/project/root/vendor/not-runescape/wow/src/TestClass.php" "")
(phpinspect-pipeline (phpinspect-find-composer-json-files fs "/project/root")
:async (lambda (res err)
(setq result res
error err))
:into (phpinspect-iterate-composer-jsons :with-context autoloader)
:into phpinspect-al-strategy-execute)
(while (not (or result error))
(thread-yield))
(should-not error)
(should-not (hash-table-empty-p (phpinspect-autoloader-own-types autoloader)))
(should-not (hash-table-empty-p (phpinspect-autoloader-types autoloader)))
(should (phpinspect-project-get-class project (phpinspect--make-type :name "\\FilesList")))
(should (string= "/project/root/vendor/runescape/client/src/Runescape/Banana/App.php"
(phpinspect-autoloader-resolve
autoloader
(phpinspect-intern-name "\\Runescape\\Banana\\App"))))
(should (string= "/project/root/vendor/not-runescape/wow/src/TestClass.php"
(phpinspect-autoloader-resolve
autoloader
(phpinspect-intern-name "\\WoW\\Dwarves\\TestClass"))))))

@ -1,91 +0,0 @@
;; -*- lexical-binding: t; -*-
(require 'phpinspect-bmap)
(ert-deftest phpinspect-bmap-overlay ()
(let ((bmap (phpinspect-make-bmap))
(bmap2 (phpinspect-make-bmap))
(bmap3 (phpinspect-make-bmap))
(token '(:token))
(token2 '(:token2))
(token3 '(:token3)))
(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))))
;; Nesting for token-starting-at
(should (eq token3 (phpinspect-meta-token
(phpinspect-bmap-token-starting-at bmap 50))))
(should (eq token3 (phpinspect-meta-token
(phpinspect-bmap-token-starting-at bmap2 55))))
(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))
(child '(:child))
(parent '(:parent))
(granny '(:granny)))
(phpinspect-bmap-register bmap 10 20 child)
(phpinspect-bmap-register bmap 5 25 parent)
(phpinspect-bmap-register bmap 2 30 granny)
(let ((child-meta (phpinspect-bmap-token-meta bmap child))
(parent-meta (phpinspect-bmap-token-meta bmap parent)))
(should (eq parent (phpinspect-meta-token
(phpinspect-meta-parent child-meta))))
(should (eq granny (phpinspect-meta-token (phpinspect-meta-parent parent-meta)))))))
(ert-deftest phpinspect-bmap-tokens-overlapping ()
(let ((bmap (phpinspect-make-bmap)))
(phpinspect-bmap-register bmap 9 20 '(:node3))
(phpinspect-bmap-register bmap 21 44 '(:node4))
(phpinspect-bmap-register bmap 20 200 '(:node2))
(phpinspect-bmap-register bmap 9 200 '(:node1))
(phpinspect-bmap-register bmap 1 300 '(:root))
(let ((result (phpinspect-bmap-tokens-overlapping bmap 22)))
(should (equal '((:node4) (:node2) (:node1))
(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,493 +0,0 @@
;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-parser)
(require 'phpinspect-buffer)
(require 'phpinspect-test-env
(expand-file-name "phpinspect-test-env.el"
(file-name-directory (macroexp-file-name))))
(ert-deftest phpinspect-buffer-region-lookups ()
(let* (parsed)
(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))
(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
`phpinspect-current-buffer' unset."
(let* ((parsed
(with-temp-buffer
(should-not phpinspect-current-buffer)
(insert-file-contents (expand-file-name "NamespacedClass.php" phpinspect-test-php-file-directory))
(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)))
(defmacro phpinspect-document-setq-local (document &rest assignments)
(declare (indent 1))
`(with-current-buffer (phpinspect-document-buffer ,document)
(setq-local ,@assignments)))
(defmacro phpinspect-with-document-buffer (document &rest body)
(declare (indent 1))
`(with-current-buffer (phpinspect-document-buffer ,document)
,@body))
(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)))
(should (member '(:word "echo") (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))))))
(ert-deftest phpinspect-buffer-parse-incrementally-position-change ()
(with-temp-buffer
(let ((buffer (phpinspect-make-buffer :buffer (current-buffer))))
(insert "<?php
declare(strict_types=1);
namespace App\\Controller\\Api\\V1;
class AccountStatisticsController {
function __construct(){}
}")
(setq-local phpinspect-test-buffer t)
(add-to-list 'after-change-functions
(lambda (start end pre-change-length)
(when (boundp 'phpinspect-test-buffer)
(phpinspect-buffer-register-edit buffer start end pre-change-length))))
(let* ((bmap (phpinspect-buffer-parse-map buffer))
(class-location 67)
(class (phpinspect-bmap-token-starting-at bmap class-location)))
(should class)
(should (phpinspect-class-p (phpinspect-meta-token class)))
(should (= class-location (phpinspect-meta-start class)))
(goto-char 65)
(let ((edit-string "use Symfony\\Component\\HttpFoundation\\JsonResponse;\n")
bmap class tokens-enclosing use-statement)
(insert edit-string)
(setq bmap (phpinspect-buffer-parse-map buffer)
class (phpinspect-bmap-token-starting-at bmap (+ 67 (length edit-string))))
(setq class-location (+ class-location (length edit-string)))
(should class)
(should (phpinspect-class-p (phpinspect-meta-token class)))
(should (= class-location (phpinspect-meta-start class)))
(setq tokens-enclosing (phpinspect-bmap-tokens-overlapping bmap class-location))
(setq class (seq-find (lambda (meta) (phpinspect-class-p (phpinspect-meta-token meta)))
tokens-enclosing))
(should class)
(should (= class-location (phpinspect-meta-start class)))
(should (phpinspect-class-p (phpinspect-meta-token class)))
(setq use-statement (phpinspect-bmap-token-starting-at bmap 65))
(should use-statement)
(should (phpinspect-use-p (phpinspect-meta-token use-statement)))
(should (seq-find #'phpinspect-use-p (seq-find #'phpinspect-namespace-p (phpinspect-buffer-tree buffer))))
(let ((second-use))
(goto-char 65)
(setq edit-string "use Another\\Use\\Statement;\n")
(insert edit-string)
(setq class-location (+ class-location (length edit-string)))
(setq bmap (phpinspect-buffer-parse-map buffer)
class (phpinspect-bmap-token-starting-at bmap class-location))
(should class)
(setq second-use (phpinspect-bmap-token-starting-at bmap 65))
(should second-use)
(setq class (phpinspect-bmap-token-starting-at bmap class-location))
(should class)
(should (= class-location (phpinspect-meta-start class)))
(should (phpinspect-class-p (phpinspect-meta-token class)))))))))
(ert-deftest phpinspect-buffer-parse-incrementally-multiedit ()
(let* ((document (phpinspect-make-document))
(buffer (phpinspect-make-buffer
:buffer (phpinspect-document-buffer document)))
parsed parsed-after current-tree)
(phpinspect-document-set-contents
document
"<?php
namespace XXX;
use ZZZ\\zzz;
class YYY {
function Foo() {
if(bar()) {
return $baz->bip->bop(bar($bim), $bom)
}
}
}")
(phpinspect-document-setq-local document
phpinspect-current-buffer buffer)
(phpinspect-with-document-buffer document
(setq buffer-undo-list nil)
(add-hook 'after-change-functions #'phpinspect-after-change-function))
(setq parsed (phpinspect-buffer-parse buffer 'no-interrupt))
;; Delete lines before class
(phpinspect-with-document-buffer document
(goto-char 40)
(kill-line)
(kill-line)
(kill-line))
(setq parsed-after (phpinspect-buffer-parse buffer 'no-interrupt))
(should (equal parsed parsed-after))
;; Delete namespace declaration
(phpinspect-with-document-buffer document
(goto-char 9)
(kill-line))
(setq parsed-after (phpinspect-buffer-parse buffer 'no-interrupt))
(setq current-tree (phpinspect-with-document-buffer document
(goto-char (point-min))
(phpinspect-parse-buffer-until-point (current-buffer) (point-max))))
(should (equal current-tree parsed-after))
;;Bring back the namespace declaration
(phpinspect-with-document-buffer document
(undo-start)
(undo-more 1))
(setq parsed-after (phpinspect-buffer-parse buffer 'no-interrupt))
(should (equal parsed parsed-after))))
(ert-deftest phpinspect-buffer-index-classes ()
(let* ((buffer (phpinspect-make-buffer :-project (phpinspect--make-project :autoload (phpinspect-make-autoloader))))
(namespaces (phpinspect-make-splayt))
(declarations (phpinspect-make-splayt))
(classes (phpinspect-make-splayt))
(root (phpinspect-make-meta nil 1 200 "" 'root)))
(phpinspect-splayt-insert
namespaces 1 (phpinspect-meta-set-parent
(phpinspect-make-meta nil 1 100 "" '(:namespace (:word "TestNamespace") (:terminator ";")))
root))
(phpinspect-splayt-insert
declarations 20
(phpinspect-make-meta nil 20 40 "" '(:declaration (:word "class") (:word "TestClass") (:word "extends") (:word "OtherTestClass"))))
(phpinspect-splayt-insert classes 20 (phpinspect-make-meta nil 20 80 "" '(:class (:comment "bla") '(:declaration (:word "class") (:word "TestClass") (:word "extends") (:word "OtherTestClass")))))
(phpinspect-buffer-index-declarations buffer declarations)
(phpinspect-buffer-index-namespaces buffer namespaces)
(phpinspect-buffer-index-classes buffer classes)
(should (phpinspect-project-get-class (phpinspect-buffer-project buffer) (phpinspect--make-type :name "\\TestNamespace\\TestClass")))
(should (= 2 (hash-table-count (phpinspect-project-class-index (phpinspect-buffer-project buffer)))))
(should (= 1 (length (phpinspect--class-extended-classes
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestNamespace\\TestClass"))))))
(let ((new-declarations (phpinspect-make-splayt))
(new-classes (phpinspect-make-splayt)))
(phpinspect-splayt-insert
new-declarations
20
(phpinspect-meta-set-parent
(phpinspect-make-meta nil 20 40 "" '(:declaration (:word "class") (:word "TestClass")))
root))
(phpinspect-splayt-insert
new-classes 20
(phpinspect-meta-set-parent
(phpinspect-make-meta nil 20 80 "" '(:class (:comment "bla") '(:declaration (:word "class") (:word "TestClass"))))
root))
(setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap :-root-meta root))
(phpinspect-buffer-index-declarations buffer new-declarations)
(phpinspect-buffer-index-classes buffer new-classes)
(should (phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestNamespace\\TestClass")))
(should (= 0 (length (phpinspect--class-extended-classes
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestNamespace\\TestClass")))))))
(let ((new-classes (phpinspect-make-splayt))
(new-root (phpinspect-make-meta nil 1 400 "" 'new-root)))
(setf (phpinspect-bmap--root-meta (phpinspect-buffer-map buffer)) new-root)
(phpinspect-buffer-index-classes buffer new-classes)
(should-not (phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestNamespace\\TestClass")))
(should (= 1 (hash-table-count (phpinspect-project-class-index (phpinspect-buffer-project buffer))))))))
(ert-deftest phpinspect-buffer-index-functions ()
(let ((buffer (phpinspect-make-buffer :-project (phpinspect--make-project :autoload (phpinspect-make-autoloader))))
(namespaces (phpinspect-make-splayt))
(declarations (phpinspect-make-splayt))
(classes (phpinspect-make-splayt))
(functions (phpinspect-make-splayt)))
(phpinspect-splayt-insert
namespaces 10
(phpinspect-make-meta nil 10 200 "" '(:namespace (:word "NS") (:terminator ";"))))
(phpinspect-splayt-insert
declarations 20
(phpinspect-make-meta nil 20 30 "" '(:declaration (:word "class") (:word "TestClass"))))
(phpinspect-splayt-insert
classes 20
(phpinspect-make-meta nil 20 70 "" '(:class (:declaration (:word "class") (:word "TestClass")))))
(phpinspect-splayt-insert
declarations 40
(phpinspect-make-meta nil 40 45 "" '(:declaration (:word "testMethod") (:list) (:word "RelativeType"))))
(phpinspect-splayt-insert
functions 40
(phpinspect-make-meta nil 40 50 "" '(:function (:declaration (:word "testMethod") (:list) (:word "RelativeType")))))
(phpinspect-buffer-index-declarations buffer declarations)
(phpinspect-buffer-index-namespaces buffer namespaces)
(phpinspect-buffer-index-classes buffer classes)
(phpinspect-buffer-index-functions buffer functions)
(should (phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\NS\\TestClass")))
(should (= 1 (hash-table-count (phpinspect--class-methods
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\NS\\TestClass"))))))
(setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap :-root-meta (phpinspect-make-meta nil 1 400 "" 'root)))
(phpinspect-buffer-index-functions buffer (phpinspect-make-splayt))
(should (= 0 (hash-table-count (phpinspect--class-methods
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\NS\\TestClass"))))))))
(ert-deftest phpinspect-buffer-index-class-variables ()
(let ((buffer (phpinspect-make-buffer :-project (phpinspect--make-project :autoload (phpinspect-make-autoloader))))
(namespaces (phpinspect-make-splayt))
(declarations (phpinspect-make-splayt))
(classes (phpinspect-make-splayt))
(functions (phpinspect-make-splayt))
(variables (phpinspect-make-splayt)))
(phpinspect-splayt-insert
functions 60
(phpinspect-make-meta
nil 60 65 ""
(cadr (phpinspect-parse-string
"<?php function __construct(array $thing) { $this->banana = $thing; }"))))
(phpinspect-splayt-insert
declarations 20
(phpinspect-make-meta nil 20 30 "" '(:declaration (:word "class") (:word "TestClass"))))
(phpinspect-splayt-insert
classes 20
(phpinspect-make-meta nil 20 70 "" '(:class (:declaration (:word "class") (:word "TestClass")))))
(phpinspect-splayt-insert
variables 33
(phpinspect-make-meta nil 33 50 "" '(:class-variable "banana")))
(phpinspect-splayt-insert
variables 54
(phpinspect-make-meta nil 54 60 "" '(:const (:word "CONSTANT"))))
(phpinspect-buffer-index-declarations buffer declarations)
(phpinspect-buffer-index-namespaces buffer namespaces)
(phpinspect-buffer-index-classes buffer classes)
(phpinspect-buffer-index-functions buffer functions)
(phpinspect-buffer-index-class-variables buffer variables)
(should (phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestClass")))
(should (= 2 (length (phpinspect--class-variables
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestClass"))))))
(should (= 1 (length (phpinspect--class-get-constants
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestClass"))))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(phpinspect--variable-type
(phpinspect--class-get-variable
(phpinspect-project-get-class
(phpinspect-buffer-project buffer)
(phpinspect--make-type :name "\\TestClass"))
"banana"))))))
(ert-deftest phpinspect-buffer-map-imports ()
(with-temp-buffer
(let ((buffer (phpinspect-make-buffer :buffer (current-buffer))))
(insert "<?php
declare(strict_types=1);
namespace App\\Controller\\Api\\V1;
use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Relations\\Relation;
use Illuminate\\Support\\Facades\\Auth;
class AccountStatisticsController {
function __construct(){}
}")
(let ((bmap (phpinspect-buffer-parse-map buffer)))
(should (equal
`((:use (:word "Illuminate\\Database\\Eloquent\\Model") (:terminator ";"))
(:use (:word "Illuminate\\Database\\Eloquent\\Relations\\Relation") (:terminator ";"))
(:use (:word "Illuminate\\Support\\Facades\\Auth") (:terminator ";")))
(mapcar #'phpinspect-meta-token
(phpinspect-splayt-to-list (phpinspect-bmap-imports bmap)))))))))

@ -1,113 +0,0 @@
;; test-class.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-class)
(require 'phpinspect-project)
(require 'subr-x)
(require 'phpinspect-worker)
(phpinspect-ensure-worker)
(ert-deftest phpinspect--merge-method-return-type ()
(let* ((class-name (phpinspect--make-type :name "\\Something"))
(method1 (phpinspect--make-function
:name "fun"
:return-type (phpinspect--make-type :name "\\array")))
(method2 (phpinspect--make-function
:name "fun"
:return-type (phpinspect--make-type :name "\\bool")))
(result (phpinspect--merge-method class-name method1 method2)))
(should (phpinspect--type= (phpinspect--make-type :name "\\bool")
(phpinspect--function-return-type result)))
(should (phpinspect--type= (phpinspect--make-type :name "\\bool")
(phpinspect--function-return-type method1)))))
(ert-deftest phpinspect-class-incorporate ()
(let ((class (phpinspect--make-class-generated))
(other-class (phpinspect--make-class-generated)))
(phpinspect--class-set-index class `(phpinspect--indexed-class (class-name . ,(phpinspect--make-type :name "Class"))))
(phpinspect--class-set-index other-class `(phpinspect--indexed-class (class-name . ,(phpinspect--make-type :name "OtherClass"))))
(phpinspect--class-update-method
class (phpinspect--make-function :name "test" :return-type phpinspect--null-type))
(phpinspect--class-update-method
other-class (phpinspect--make-function :name "other-test" :return-type phpinspect--null-type))
(phpinspect--class-incorporate class other-class)
(should (= 2 (length (hash-table-values (phpinspect--class-methods class)))))
(should (= 1 (length (hash-table-values (phpinspect--class-subscriptions other-class)))))
(phpinspect--class-set-index
class
`(phpinspect--indexed-class
(complete . t)
(class-name . ,(phpinspect--make-type :name "Class"))
(methods . ,(list (phpinspect--make-function :name "test" :return-type phpinspect--null-type)
(phpinspect--make-function :name "foobar" :return-type phpinspect--null-type)))))
(should (= 3 (length (hash-table-values (phpinspect--class-methods class)))))
(phpinspect--class-incorporate class other-class)
(should (= 3 (length (hash-table-values (phpinspect--class-methods class)))))
(phpinspect--class-set-index
class
`(phpinspect--indexed-class
(complete . t)
(class-name . ,(phpinspect--make-type :name "Class"))
(methods . ,(list (phpinspect--make-function :name "foobar" :return-type phpinspect--null-type)))))
(should (= 2 (length (hash-table-values (phpinspect--class-methods class)))))
(should (phpinspect--class-get-method class (phpinspect-intern-name "other-test")))
(should (phpinspect--class-get-method class (phpinspect-intern-name "foobar")))
(phpinspect--class-set-index
class
`(phpinspect--indexed-class
(complete . t)
(class-name . ,(phpinspect--make-type :name "Class"))
(methods)))
(should (= 1 (length (hash-table-values (phpinspect--class-methods class)))))
(should (phpinspect--class-get-method class (phpinspect-intern-name "other-test")))
(phpinspect--class-incorporate class other-class)
(should (= 1 (length (hash-table-values (phpinspect--class-methods class)))))
(should (= 1 (length (hash-table-values (phpinspect--class-subscriptions other-class)))))))
(ert-deftest phpinspect--class-update-declaration ()
(let ((class (phpinspect--make-class-generated
:class-retriever (phpinspect-project-make-class-retriever
(phpinspect--make-project)))))
(phpinspect--class-update-declaration class '(:declaration (:word "class") (:word "TestClass")
(:word "extends") (:word "OtherClass")
(:word "implements") (:word "ImplClass"))
nil "NS")
(should (= 2 (length (phpinspect--class-extended-classes class))))
(should (phpinspect--type= (phpinspect--make-type :name "\\NS\\TestClass" :fully-qualified t)
(phpinspect--class-name class)))))

@ -1,134 +0,0 @@
;;; test-edtrack.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
(require 'ert)
(require 'phpinspect-edtrack)
(require 'phpinspect-meta)
(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)))
(phpinspect-edtrack-register-edit edtrack 5 10 10)
(phpinspect-edtrack-register-edit edtrack 100 200 150)
(phpinspect-edtrack-register-edit edtrack 15 22 7)
(should (equal `((255 . -50) (27 . 0) (15 . -5)) (phpinspect-edtrack-edits edtrack)))))
(ert-deftest phpinspect-edtrack-register-encroaching-edit ()
(let* ((edtrack (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit edtrack 5 10 0)
(phpinspect-edtrack-register-edit edtrack 100 150 25)
;; Encroaches on delta of edit before by 15 points ((125 + 25) - 135 = 15),
;; so the original end position should be calculated as 135 - (25 - 15) - 5 = 120
;; (see also `phpinspect-edtrack-original-position-at-point')
(phpinspect-edtrack-register-edit edtrack 135 170 0)
(should (equal `((120 . 35) (120 . 25) (5 . 5)) (phpinspect-edtrack-edits edtrack)))))
(ert-deftest phpinspect-edtrack-orginal-position-at-point ()
(let ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit track 10 20 0)
(should (= 10 (phpinspect-edtrack-original-position-at-point track 20)))
(should (= 10 (phpinspect-edtrack-original-position-at-point track 15)))
(phpinspect-edtrack-register-edit track 30 40 5)
(should (= 35 (phpinspect-edtrack-original-position-at-point track 50)))
(should (= 25 (phpinspect-edtrack-original-position-at-point track 39)))))
(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)))))
(ert-deftest phpinspect-edtrack-edit-derived-taint-iterator ()
(let ((track (phpinspect-make-edtrack))
iterator)
(phpinspect-edtrack-register-edit track 10 20 5)
(phpinspect-edtrack-register-edit track 15 30 0)
(phpinspect-edtrack-register-edit track 20 25 10)
(setq iterator (phpinspect-edtrack-make-taint-iterator track))
(should (phpinspect-taint-iterator-region-is-tainted-p iterator 15 20))
(should (phpinspect-taint-iterator-region-is-tainted-p iterator 25 30))
(should-not (phpinspect-taint-iterator-region-is-tainted-p iterator 30 35))))
(ert-deftest phpinspect-edtrack-taint-overlapping-edits ()
(let ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit track 10 20 5)
(should (equal (list (cons 10 15)) (phpinspect-edtrack-taint-pool track)))
(phpinspect-edtrack-register-edit track 15 0 1)
(should (equal (list (cons 10 16)) (phpinspect-edtrack-taint-pool track)))))
(ert-deftest phpinspect-edtrack-register-multi-edits-same-start ()
(let ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit track 10 11 0)
(phpinspect-edtrack-register-edit track 10 10 1)
(should (equal (list (cons 10 -1) (cons 10 1)) (phpinspect-edtrack-edits track)))))
(ert-deftest phpinspect-edtrack-undo ()
(let ((track (phpinspect-make-edtrack)))
(phpinspect-edtrack-register-edit track 10 10 10)
(phpinspect-edtrack-register-edit track 10 10 10)
(phpinspect-edtrack-register-edit track 10 30 0)
(should (= 30 (phpinspect-edtrack-original-position-at-point track 30)))
(should (= 20 (phpinspect-edtrack-original-position-at-point track 20)))
(should (= 15 (phpinspect-edtrack-original-position-at-point track 15)))
(should (= 35 (phpinspect-edtrack-original-position-at-point track 35)))
(should (= 10 (phpinspect-edtrack-original-position-at-point track 10)))))

@ -1,113 +0,0 @@
;;; test-eldoc.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
(require 'phpinspect)
(require 'phpinspect-eldoc)
(ert-deftest phpinspect-eld-method-call ()
(with-temp-buffer
(phpinspect-ensure-worker)
(phpinspect-purge-cache)
(let* ((php-code "
class Thing
{
function getThis(\\DateTime $moment, Thing $thing, $other): static
{
return $this;
}
function doStuff()
{
$this->getThis(new \\DateTime(), bla)")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(buffer (phpinspect-make-buffer :buffer (current-buffer)))
second-arg-pos inside-nested-list-pos first-arg-pos)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(insert php-code)
(backward-char)
(setq second-arg-pos (point))
(backward-char 6)
(setq inside-nested-list-pos (point))
(backward-char 8)
(setq first-arg-pos (point))
(let ((result (phpinspect-eldoc-query-execute
(phpinspect-make-eldoc-query :point second-arg-pos :buffer buffer))))
(should (phpinspect-function-doc-p result))
(should (= 1 (phpinspect-function-doc-arg-pos result)))
(should (string= "getThis" (phpinspect--function-name (phpinspect-function-doc-fn result))))
(setq result (phpinspect-eldoc-query-execute
(phpinspect-make-eldoc-query :point inside-nested-list-pos :buffer buffer)))
(should-not result)
(setq result (phpinspect-eldoc-query-execute
(phpinspect-make-eldoc-query :point first-arg-pos :buffer buffer)))
(should (phpinspect-function-doc-p result))
(should (= 0 (phpinspect-function-doc-arg-pos result)))
(should (string= "getThis" (phpinspect--function-name (phpinspect-function-doc-fn result))))))))
(ert-deftest phpinspect-eldoc-function-for-object-method ()
(phpinspect-purge-cache)
(let* ((php-code "
class Thing
{
function getThis(\\DateTime $moment, Thing $thing, $other): static
{
return $this;
}
function doStuff()
{
$this->getThis(new \\DateTime(), bla)")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100))
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(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 ()
(phpinspect-purge-cache)
(let* ((php-code "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
{
return $this;
}
function doStuff()
{
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))
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(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))))))

@ -1,87 +0,0 @@
;;; test-autoload.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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-fs)
(ert-deftest phpinspect-virtual-fs-file-exists-p ()
(let ((fs (phpinspect-make-virtual-fs)))
(phpinspect-virtual-fs-set-file fs "/test/test.txt" "contents")
(should (phpinspect-fs-file-exists-p fs "/test/test.txt"))))
(ert-deftest phpinspect-virtual-fs-insert-file-contents ()
(let ((fs (phpinspect-make-virtual-fs)))
(phpinspect-virtual-fs-set-file fs "/test/test.txt" "contents")
(with-temp-buffer
(phpinspect-fs-insert-file-contents fs "/test/test.txt")
(should (string= "contents" (buffer-string))))
(with-temp-buffer
(phpinspect-fs-insert-file-contents fs "/test/nonexistant")
(should (string= "" (buffer-string))))))
(ert-deftest phpinspect-virtual-fs-directory-files-and-recursively ()
(let ((fs (phpinspect-make-virtual-fs)))
(puthash "/test/test.txt" "contents" (phpinspect-virtual-fs-files fs))
(puthash "/a/b/c/dee.match" "contents" (phpinspect-virtual-fs-files fs))
(puthash "/a/b/c/cee.match" "contents" (phpinspect-virtual-fs-files fs))
(puthash "/a/b/c/aaa.match" "contents" (phpinspect-virtual-fs-files fs))
(puthash "/a/b/c/nope.nomatch" "contents" (phpinspect-virtual-fs-files fs))
(puthash "/a/b/d/jee.match" "contents" (phpinspect-virtual-fs-files fs))
(let ((files (phpinspect-fs-directory-files fs "/test/")))
(should (equal '("/test/test.txt") files)))
(let ((files (phpinspect-fs-directory-files fs "/a/b/c")))
(should (member "/a/b/c/dee.match" files))
(should (member "/a/b/c/cee.match" files))
(should (member "/a/b/c/aaa.match" files))
(should (member "/a/b/c/nope.nomatch" files)))
(let ((files (phpinspect-fs-directory-files fs "/a/b/c" "\\.match$")))
(should (member "/a/b/c/dee.match" files))
(should (member "/a/b/c/cee.match" files))
(should (member "/a/b/c/aaa.match" files))
(should (not (member "/a/b/c/nope.nomatch" files))))
(let ((files (phpinspect-fs-directory-files fs "/a/b")))
(should (member "/a/b/c" files))
(should (member "/a/b/d" files))
(should (= 2 (length files))))
(let ((files (phpinspect-fs-directory-files-recursively fs "/a/b")))
(should (member "/a/b/c/dee.match" files))
(should (member "/a/b/c/cee.match" files))
(should (member "/a/b/c/aaa.match" files))
(should (member "/a/b/c/nope.nomatch" files))
(should (member "/a/b/d/jee.match" files)))
(let ((files (phpinspect-fs-directory-files-recursively fs "/a/b" "\\.match$")))
(should (member "/a/b/c/dee.match" files))
(should (member "/a/b/c/cee.match" files))
(should (member "/a/b/c/aaa.match" files))
(should (not (member "/a/b/c/nope.nomatch" files)))
(should (member "/a/b/d/jee.match" files)))))

@ -1,250 +0,0 @@
;;; test-index.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-index)
(require 'phpinspect-parse-context)
(require 'phpinspect-bmap)
(require 'phpinspect-parser)
(require 'phpinspect-test-env
(expand-file-name "phpinspect-test-env.el"
(file-name-directory (macroexp-file-name))))
(ert-deftest phpinspect-index-static-methods ()
(let* ((class-tokens
`(:root
(:class
(:declaration (:word "class") (:word "Potato"))
(:block
(:static
(:function (:declaration (:word "function")
(:word "staticMethod")
(:list (:variable "untyped")
(:comma)
(:word "array")
(:variable "things")))
(:block)))))))
(index (phpinspect--index-tokens class-tokens))
(expected-index
`(phpinspect--root-index
(imports)
(classes
(,(phpinspect--make-type :name "\\Potato" :fully-qualified t)
phpinspect--indexed-class
(complete . t)
(class-name . ,(phpinspect--make-type :name "\\Potato" :fully-qualified t))
(declaration . (:declaration (:word "class") (:word "Potato")))
(location . (0 0))
(imports)
(methods)
(static-methods . (,(phpinspect--make-function
:name "staticMethod"
:scope '(:public)
:token '(:function (:declaration (:word "function")
(:word "staticMethod")
(:list (:variable "untyped")
(:comma)
(:word "array")
(:variable "things")))
(:block))
:arguments `(("untyped" nil)
("things" ,(phpinspect--make-type :name "\\array"
:collection t
:fully-qualified t)))
:return-type phpinspect--null-type)))
(static-variables)
(variables)
(constants)
(extends)
(implements)
(used-types . (,(phpinspect-intern-name "array")))))
(used-types)
(functions))))
(should (equal expected-index index))))
(ert-deftest phpinspect-index-used-types-in-class ()
(let* ((result (phpinspect--index-tokens
(phpinspect-parse-string
"<?php namespace Field; class Potato extends Cheese, Bacon implements Ham, Bagel {
public function makeThing(): Thing
{
if ((new Monkey())->tree() === true) {
return new ExtendedThing();
}
return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->antiPotato(new OtherThing()))));
}")))
(used-types (alist-get 'used-types (car (alist-get 'classes result)))))
(should (equal
(mapcar #'phpinspect-intern-name
(sort
(copy-sequence
'("Cheese" "Bacon" "Ham" "Bagel" "Monkey" "ExtendedThing"
"StaticThing" "Thing" "ThingFactory" "Potato" "OtherThing"))
#'string<))
(sort used-types (lambda (s1 s2) (string< (phpinspect-name-string s1) (phpinspect-name-string s2))))))))
(ert-deftest phpinspect--find-used-types-in-tokens ()
(let ((blocks `(
((:block (:word "return")
(:word "new")
(:word "Response")
(:list))
("Response"))
((:block (:list (:word "new") (:word "Response"))
(:object-attrib (:word "someMethod")
(:list (:word "new")
(:word "Request"))))
("Request" "Response")))))
(dolist (set blocks)
(let ((result (phpinspect--find-used-types-in-tokens (car set))))
(should (equal (sort (copy-sequence (cadr set)) #'string-lessp) (sort result #'string-lessp)))))))
(ert-deftest phpinspect-index-method-annotations ()
(let* ((result (phpinspect--index-tokens
(phpinspect-parse-string
"<?php
/* @method int peel(bool $fast, array $loose)
* @method Banana duplicate()
@method hold() **/
class Banana {}")))
(class (car (alist-get 'classes result)))
(methods (alist-get 'methods class)))
(should (= 3 (length methods)))
(dolist (method methods)
(should (member (phpinspect--function-name method)
'("duplicate" "hold" "peel")))
(cond ((string= (phpinspect--function-name method)
"duplicate")
(should (phpinspect--type=
(phpinspect--make-type :name "\\Banana" :fully-qualified t)
(phpinspect--function-return-type method))))
((string= (phpinspect--function-name method)
"peel")
(should (phpinspect--type=
(phpinspect--make-type :name "\\int" :fully-qualified t)
(phpinspect--function-return-type method)))
(should (= 2 (length (phpinspect--function-arguments method))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\array" :fully-qualified t)
(car (alist-get
"loose" (phpinspect--function-arguments method) nil nil #'string=))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\bool" :fully-qualified t)
(car (alist-get
"fast" (phpinspect--function-arguments method) nil nil #'string=)))))
((string= (phpinspect--function-name method)
"hold")
(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 :bmap (phpinspect-make-bmap)))
(tree))
(with-temp-buffer
(insert-file-contents (expand-file-name "IndexClass1.php" phpinspect-test-php-file-directory))
(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)))))
(ert-deftest phpinspect-index-functions ()
(let* ((code "<?php
use Example\\Thing;
function test_func(): array {}
function example(): Thing {}")
(tokens (phpinspect-parse-string code))
(index (phpinspect--index-tokens tokens))
functions)
(should (setq functions (alist-get 'functions index)))
(should (= 2 (length functions)))
(should (string= "test_func" (phpinspect--function-name (cadr functions))))
(should (string= "example" (phpinspect--function-name (car functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(phpinspect--function-return-type (cadr functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Example\\Thing")
(phpinspect--function-return-type (car functions))))))
(ert-deftest phpinspect-index-functions-in-namespace ()
(let* ((code "<?php
namespace Local;
use Example\\Thing;
function test_func(): array {}
function example(Firewall $wall): Thing {}")
(tokens (phpinspect-parse-string code))
(index (phpinspect--index-tokens tokens))
functions)
(should (setq functions (alist-get 'functions index)))
(should (= 2 (length functions)))
(should (string= "Local\\test_func" (phpinspect--function-name (cadr functions))))
(should (string= "Local\\example" (phpinspect--function-name (car functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(phpinspect--function-return-type (cadr functions))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Example\\Thing")
(phpinspect--function-return-type (car functions))))
(should (= 3 (length (alist-get 'used-types index))))
(should (member (phpinspect-intern-name "Firewall") (alist-get 'used-types index)))
(should (member (phpinspect-intern-name "array") (alist-get 'used-types index)))
(should (member (phpinspect-intern-name "Thing") (alist-get 'used-types index)))
(should (alist-get 'used-types index))))

@ -1,42 +0,0 @@
;; test-meta.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-meta)
(ert-deftest phpinspect-meta-start-relative-to-parent ()
(let ((meta (phpinspect-make-meta nil 10 20 "" 'token))
(parent1 (phpinspect-make-meta nil 9 22 "" 'token))
(parent2 (phpinspect-make-meta nil 0 100 "" 'token)))
(phpinspect-meta-set-parent meta parent1)
(phpinspect-meta-set-parent parent1 parent2)
(should (= 10 (phpinspect-meta-start meta)))
(phpinspect-meta-shift parent2 20)
(should (= 30 (phpinspect-meta-start meta)))
(should (phpinspect-meta-overlaps-point meta 30))))

@ -1,53 +0,0 @@
;;; phpinspect-test.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-parse-context)
(require 'phpinspect-meta)
(require 'phpinspect-bmap)
(ert-deftest phpinspect-pctx-cancel ()
(let ((meta (phpinspect-make-meta nil 10 20 " " 'token 'overlay nil))
(pctx (phpinspect-make-pctx :bmap (phpinspect-make-bmap))))
(phpinspect-with-parse-context pctx
(phpinspect-meta-with-changeset meta
(setf (phpinspect-meta-absolute-start meta) 222)
(setf (phpinspect-meta-absolute-end meta) 1234)
(phpinspect-meta-set-parent meta (phpinspect-make-meta nil 1 2000 "" 'parent-token))
(setf (phpinspect-meta-overlay meta) 'not-overlay)))
(should (= 222 (phpinspect-meta-start meta)))
(should (= 1234 (phpinspect-meta-end meta)))
(should (phpinspect-meta-parent meta))
(should (eq 'not-overlay (phpinspect-meta-overlay meta)))
(should (= 221 (phpinspect-meta-parent-offset meta)))
(phpinspect-pctx-cancel pctx)
(should (= 10 (phpinspect-meta-start meta)))
(should (= 20 (phpinspect-meta-end meta)))
(should-not (phpinspect-meta-parent meta))
(should-not (phpinspect-meta-parent-offset meta))
(should (eq 'overlay (phpinspect-meta-overlay meta)))))

@ -1,146 +0,0 @@
;; test-parser.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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-parser)
(require 'phpinspect-index)
(require 'phpinspect-test-env
(expand-file-name "phpinspect-test-env.el"
(file-name-directory (macroexp-file-name))))
(ert-deftest phpinspect-parse-bmap ()
(let* ((ctx (phpinspect-make-pctx :incremental t :bmap (phpinspect-make-bmap)))
(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)))))))
(ert-deftest phpinspect-parse-comma ()
(let* ((code "(,)")
(ctx (phpinspect-make-pctx :incremental t :bmap (phpinspect-make-bmap)))
(parsed (phpinspect-with-parse-context ctx
(phpinspect-parse-string code)))
(comma (cadadr parsed))
(map (phpinspect-pctx-bmap ctx))
(comma-meta))
(should (equal '(:root (:list (:comma ","))) parsed))
(should (equal '(:comma ",") comma))
(should (setq comma-meta (phpinspect-bmap-token-meta map comma)))
(should (= 1 (phpinspect-meta-width comma-meta)))))
(ert-deftest phpinspect-parse-namespaced-class ()
"Test phpinspect-parse on a namespaced class"
(should
(equal (phpinspect-test-read-fixture-data "NamespacedClass")
(phpinspect-test-parse-fixture-code "NamespacedClass"))))
(ert-deftest phpinspect-parse-block ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Block")
(phpinspect-test-parse-fixture-code "Block"))))
(ert-deftest phpinspect-parse-functions ()
"Test phpinspect-parse for php functions"
(should
(equal (phpinspect-test-read-fixture-data "Functions")
(phpinspect-test-parse-fixture-code "Functions"))))
(ert-deftest phpinspect-parse-namespaced-functions ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "NamespacedFunctions")
(phpinspect-test-parse-fixture-code "NamespacedFunctions"))))
(ert-deftest phpinspect-parse-variable ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Variable")
(phpinspect-test-parse-fixture-code "Variable"))))
(ert-deftest phpinspect-parse-word ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Word")
(phpinspect-test-parse-fixture-code "Word"))))
(ert-deftest phpinspect-parse-array ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "Array")
(phpinspect-test-parse-fixture-code "Array"))))
(ert-deftest phpinspect-parse-short-function ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "ShortFunction")
(phpinspect-test-parse-fixture-code "ShortFunction"))))
(ert-deftest phpinspect-parse-two-short-functions ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "TwoShortFunctions")
(phpinspect-test-parse-fixture-code "TwoShortFunctions"))))
(ert-deftest phpinspect-parse-small-namespaced-class ()
"Test phpinspect-parse for php blocks"
(should
(equal (phpinspect-test-read-fixture-data "SmallNamespacedClass")
(phpinspect-test-parse-fixture-code "SmallNamespacedClass"))))
;; If this test fails, the syntax tree has a breaking change in it. Regenerate the
;; fixtures and fix anything that is broken.
(ert-deftest phpinspect-syntax-tree-change ()
(let ((index (phpinspect--index-tokens
(phpinspect-test-parse-fixture-code "IndexClass1")))
(expected-result (phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1"))))
(should (equal index expected-result))))

@ -1,66 +0,0 @@
;;; test-pipeline.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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-pipeline)
(defun phpinspect--correct-the-record (input)
(phpinspect-pipeline-emit
(format "It's not %s, but GNU/%s" input input)))
(ert-deftest phpinspect-pipeline ()
(let (result error)
(phpinspect-pipeline (list "Linux" "Emacs")
:into phpinspect--correct-the-record
:async (lambda (res err)
(setq result res
error err)))
(while (not (or result error))
(thread-yield))
(should (equal '("It's not Linux, but GNU/Linux" "It's not Emacs, but GNU/Emacs")
result))
(should-not error)))
(defun phpinspect--aah-it-broke (input)
(signal 'it-brokey input))
(ert-deftest phpinspect-pipeline-error ()
(let (result error)
(phpinspect-pipeline (list "Holy smokey")
:into phpinspect--aah-it-broke
:async (lambda (res err)
(setq result res
error err)))
(while (not (or result error))
(thread-yield))
(should error)
(should (equal '(phpinspect-pipeline-error
"Thread phpinspect-pipeline-phpinspect--aah-it-broke signaled error: (it-brokey . Holy smokey)")
error))))

@ -1,44 +0,0 @@
;; test-project.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-project)
(ert-deftest phpinspect-project-purge ()
(let ((project (phpinspect--make-project)))
(phpinspect-project-purge project)
(should (eq t (phpinspect-project-purged project)))))
(ert-deftest phpinspect-project-watch-file-and-purge ()
(let* ((root (make-temp-file "phpinspect-test" 'dir))
(fs (phpinspect-make-fs))
(watch-file (concat root "/watch1"))
(project (phpinspect--make-project :fs fs :root root)))
(phpinspect-project-watch-file project watch-file #'ignore)
(phpinspect-project-purge project)
(should (= 0 (length (hash-table-values (phpinspect-project-file-watchers project)))))))

@ -1,112 +0,0 @@
;; -*- lexical-binding: t; -*-
(require 'phpinspect-resolvecontext)
(require 'phpinspect)
(require 'phpinspect-test-env
(expand-file-name "phpinspect-test-env.el"
(file-name-directory (macroexp-file-name))))
(ert-deftest phinspect-get-resolvecontext ()
(let* ((ctx (phpinspect-make-pctx :incremental t :bmap (phpinspect-make-bmap)))
(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 (expand-file-name "IncompleteClass.php" phpinspect-test-php-file-directory))
(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 ()
(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"))))))

@ -1,186 +0,0 @@
;; test-splayt.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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-splayt)
(ert-deftest phpinspect-splayt-node-rotate ()
(let* ((one (phpinspect-make-splayt-node 1 "one"))
(four (phpinspect-make-splayt-node 4 "four"))
(three (phpinspect-make-splayt-node
3 "three"
one
four)))
(setf (phpinspect-splayt-node-parent four) three)
(setf (phpinspect-splayt-node-parent one) three)
(phpinspect-splayt-node-rotate-right three)
(should (eq one (phpinspect-splayt-node-parent three)))
(should (eq three (phpinspect-splayt-node-parent four)))
(should (eq three (phpinspect-splayt-node-right one)))
(should (eq four (phpinspect-splayt-node-right three)))
(should-not (phpinspect-splayt-node-left one))
(should-not (phpinspect-splayt-node-left four))
(should-not (phpinspect-splayt-node-left three))
(phpinspect-splayt-node-rotate-left one)
(should (eq one (phpinspect-splayt-node-left three)))
(should (eq three (phpinspect-splayt-node-parent four)))
(should (eq three (phpinspect-splayt-node-parent one)))
(should (eq four (phpinspect-splayt-node-right three)))
(should (eq one (phpinspect-splayt-node-left three)))))
(ert-deftest phpinspect-splayt ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(should (string= "eight" (phpinspect-splayt-find tree 8)))
(should (string= "one" (phpinspect-splayt-find tree 1)))
(should (string= "three" (phpinspect-splayt-find tree 3)))
(should (string= "nine" (phpinspect-splayt-find tree 9)))
(should (string= "four" (phpinspect-splayt-find tree 4)))
(should (string= "twelve" (phpinspect-splayt-find tree 12)))
(should (string= "eleven" (phpinspect-splayt-find tree 11)))
(let ((expected (sort (copy-sequence '("nine" "three" "eleven" "eight" "twelve" "four" "one")) #'string-lessp))
(result))
(phpinspect-splayt-traverse (item tree)
(push item result))
(setq result (sort result #'string-lessp))
(should (equal expected result)))))
(ert-deftest phpinspect-splayt-traverse ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(let ((expected (sort (copy-sequence '("nine" "three" "eleven" "eight" "twelve" "four" "one")) #'string-lessp))
(result))
(phpinspect-splayt-traverse (item tree)
(push item result))
(setq result (sort result #'string-lessp))
(should (equal expected result)))))
(ert-deftest phpinspect-splayt-traverse-lr ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(let ((expected '("one" "three" "four" "eight" "nine" "eleven" "twelve"))
result)
(phpinspect-splayt-traverse-lr (item tree)
(setq result (nconc result (list item))))
(should (equal expected result)))))
(ert-deftest phpinspect-splayt-find-smallest-after ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(should (string= "nine" (phpinspect-splayt-find-smallest-after tree 8)))
(should (string= "three" (phpinspect-splayt-find-smallest-after tree 1)))))
(ert-deftest phpinspect-splayt-find-largest-before ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(should (string= "four" (phpinspect-splayt-find-largest-before tree 8)))
(should (string= "eleven" (phpinspect-splayt-find-largest-before tree 12)))
(should (string= "one" (phpinspect-splayt-find-largest-before tree 2)))
(should (string= "twelve" (phpinspect-splayt-find-largest-before tree 13)))))
(ert-deftest phpinspect-splayt-find-all-after ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(should (equal (sort (copy-sequence '("eight" "nine" "eleven" "twelve")) #'string-lessp)
(sort (phpinspect-splayt-find-all-after tree 7) #'string-lessp)))))
(ert-deftest phpinspect-splayt-to-list ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 1 "one")
(phpinspect-splayt-insert tree 2 "two")
(should (equal '("one" "two" "three") (phpinspect-splayt-to-list tree)))))
(ert-deftest phpinspect-splayt-find-all-between ()
(let ((tree (phpinspect-make-splayt)))
(phpinspect-splayt-insert tree 9 "nine")
(phpinspect-splayt-insert tree 3 "three")
(phpinspect-splayt-insert tree 11 "eleven")
(phpinspect-splayt-insert tree 8 "eight")
(phpinspect-splayt-insert tree 12 "twelve")
(phpinspect-splayt-insert tree 4 "four")
(phpinspect-splayt-insert tree 1 "one")
(should (equal '("three" "four") (phpinspect-splayt-find-all-between tree 1 5)))))

@ -1,57 +0,0 @@
;;; test-edtrack.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
(require 'phpinspect-toc)
(require 'phpinspect-splayt)
(require 'phpinspect-meta)
(ert-deftest phpinspect-make-toc ()
(let ((tokens (phpinspect-make-splayt))
toc)
(phpinspect-splayt-insert tokens 1 (phpinspect-make-meta nil 1 20 "" 'token1))
(phpinspect-splayt-insert tokens 40 (phpinspect-make-meta nil 40 45 "" 'token2))
(phpinspect-splayt-insert tokens 55 (phpinspect-make-meta nil 55 70 "" 'token3))
(setq toc (phpinspect-make-toc tokens))
(should (= 3 (hash-table-count (phpinspect-toc-table toc))))
(should (= 3 (length (phpinspect-splayt-to-list (phpinspect-toc-tree toc)))))))
(ert-deftest phpinspect-update-toc ()
(let ((tokens (phpinspect-make-splayt))
(root (phpinspect-make-meta nil 1 200 "" 'root))
(new-root (phpinspect-make-meta nil 1 400 "" 'root))
(tok1 (phpinspect-make-meta nil 1 20 "" 'token1))
(tok2 (phpinspect-make-meta nil 40 45 "" 'token2))
(tok3 (phpinspect-make-meta nil 55 70 "" 'token3))
(tok4 (phpinspect-make-meta nil 71 91 "" 'token4))
new-tokens toc)
(phpinspect-meta-set-parent tok1 root)
(phpinspect-meta-set-parent tok2 root)
(phpinspect-meta-set-parent tok3 root)
(phpinspect-splayt-insert tokens 1 tok1)
(phpinspect-splayt-insert tokens 40 tok2)
(phpinspect-splayt-insert tokens 55 tok3)
(setq toc (phpinspect-make-toc tokens))
(phpinspect-meta-set-parent tok2 new-root)
(phpinspect-meta-set-parent tok3 new-root)
(phpinspect-meta-set-parent tok4 new-root)
(setq new-tokens (phpinspect-make-splayt))
(phpinspect-splayt-insert new-tokens 71 tok4)
(pcase-let ((`(,result-new ,result-deleted) (phpinspect-toc-update toc new-tokens new-root)))
(should (= 1 (length result-new)))
(should (= 1 (length result-deleted)))
(should (eq tok1 (car result-deleted)))
(should (eq tok4 (car result-new))))
(should (equal '(token2 token3)
(mapcar #'phpinspect-meta-token (phpinspect-toc-tokens-in-region toc 0 70))))
(should (equal '(token2 token3 token4)
(mapcar #'phpinspect-meta-token (phpinspect-toc-tokens-in-region toc 0 91))))))

@ -1,38 +0,0 @@
;; test-type.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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-type)
(ert-deftest phpinspect--resolve-late-static-binding ()
(let* ((sets '(("\\bool" . "\\bool")
("\\static" . "\\AType")
("\\this" . "\\AType"))))
(dolist (set sets)
(should (phpinspect--type=
(phpinspect--resolve-late-static-binding
(phpinspect--make-type :name (car set))
(phpinspect--make-type :name "\\AType"))
(phpinspect--make-type :name (cdr set)))))))

@ -1,57 +0,0 @@
;;; test-util.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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-util)
(ert-deftest phpinspect--pattern ()
(let* ((a "a")
(pattern1 (phpinspect--make-pattern :m `(,a) :m * :m "b"))
(pattern2 (phpinspect--make-pattern :f #'listp :m * :m "b")))
(should (equal '(:m ("a") :m * :m "b") (phpinspect--pattern-code pattern1)))
(should (equal '(:f listp :m * :m "b") (phpinspect--pattern-code pattern2)))
(dolist (pattern `(,pattern1 ,pattern2))
(should (phpinspect--pattern-match pattern '(("a") "c" "b")))
(should (equal '(("a") "c" "b") (phpinspect--pattern-match pattern '(("a") "c" "b"))))
(should (phpinspect--pattern-match pattern '(("a") nil "b")))
(should-not (phpinspect--pattern-match pattern '(1 nil "b")))
(should-not (phpinspect--pattern-match pattern '(("a") nil "b" "c"))))))
(ert-deftest phpinspect--pattern-concat ()
(let* ((pattern1 (phpinspect--make-pattern :m "a" :m * :m "b"))
(pattern2 (phpinspect--make-pattern :f #'stringp :m * :m "D"))
(result (phpinspect--pattern-concat pattern1 pattern2)))
(should (equal '(:m "a" :m * :m "b" :f stringp :m * :m "D") (phpinspect--pattern-code result)))
(should (phpinspect--pattern-match result '("a" "anything" "b" "astring" nil "D")))))
(ert-deftest phpinspect--pattern-match-partially ()
(let ((result (phpinspect--match-sequence '((:variable "this") (:object-attrib (:word "em")))
:m '(:variable "this")
:m '(:object-attrib (:word "not-a-match")))))
(should-not result)))

@ -1,93 +0,0 @@
;;; test-worker.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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 'ert)
(require 'phpinspect-index)
(require 'phpinspect-worker)
(ert-deftest phpinspect-queue-enqueue ()
(let ((queue (phpinspect-make-queue)))
(phpinspect-queue-enqueue queue "one")
(phpinspect-queue-enqueue queue "two")
(phpinspect-queue-enqueue queue "three")
(should (string= "one" (phpinspect-queue-dequeue queue)))
(should (string= "two" (phpinspect-queue-dequeue queue)))
(should (string= "three" (phpinspect-queue-dequeue queue)))
(should-not (phpinspect-queue-dequeue queue))))
(ert-deftest phpinspect-queue-subscribe ()
(let ((be-called nil))
(let ((queue (phpinspect-make-queue (lambda () (setq be-called t)))))
(phpinspect-queue-enqueue queue "one"))
(should be-called)))
(ert-deftest phpinspect-queue-find ()
(let ((queue (phpinspect-make-queue)))
(phpinspect-queue-enqueue queue "one")
(phpinspect-queue-enqueue queue "two")
(phpinspect-queue-enqueue queue "three")
(should (string= "one" (phpinspect-queue-find queue "one" 'string=)))
(should (string= "two" (phpinspect-queue-find queue "two" 'string=)))
(should (string= "three" (phpinspect-queue-find queue "three" 'string=)))))
(ert-deftest phpinspect-doqueue ()
;; Iterate over a populated queue
(let ((queue (phpinspect-make-queue)))
(phpinspect-queue-enqueue queue "one")
(phpinspect-queue-enqueue queue "two")
(phpinspect-queue-enqueue queue "three")
(phpinspect-queue-enqueue queue "four")
(let ((expected-things '("one" "two" "three" "four"))
(things))
(phpinspect-doqueue (thing queue)
(push thing things))
(should (equal expected-things (nreverse things)))))
;; attempt to iterate over an empty queue
(let ((have-iterated nil))
(phpinspect-doqueue (_thing (phpinspect-make-queue))
(setq have-iterated t))
(should-not have-iterated)))
(ert-deftest phpinspect-queue-enqueue-noduplicate ()
(let ((queue (phpinspect-make-queue))
(expected-things '("one" "two"))
(things))
(phpinspect-queue-enqueue-noduplicate queue "one" 'string=)
(phpinspect-queue-enqueue-noduplicate queue "two" 'string=)
(phpinspect-queue-enqueue-noduplicate queue "two" 'string=)
(phpinspect-queue-enqueue-noduplicate queue "one" 'string=)
(phpinspect-doqueue (thing queue)
(push thing things))
(should (equal expected-things (nreverse things)))))

@ -1,12 +1,12 @@
(require 'phpinspect)
(require 'phpinspect-index)
(require 'phpinspect-serialize)
(let ((here (file-name-directory (macroexp-file-name)))
(let ((here (file-name-directory
(or load-file-name
buffer-file-name)))
(print-length 1000)
(print-level 1000))
(dolist (file (directory-files (expand-file-name "../fixtures" here) t "\\.php\\'"))
(dolist (file (directory-files (concat here "/../fixtures" ) t "\\.php$"))
(with-temp-buffer
(insert-file-contents-literally file)
(let ((result (phpinspect-parse-current-buffer)))
@ -20,6 +20,5 @@
(insert-file-contents-literally (concat here "/../fixtures/" class ".eld"))
(read (current-buffer)))))
(with-temp-buffer
(insert (prin1-to-string (phpinspect--serialize-root-index
(phpinspect--index-tokens index-class))))
(insert (prin1-to-string (phpinspect--index-tokens index-class)))
(write-file (concat here "/../fixtures/" class "-indexed.eld"))))))

Loading…
Cancel
Save