Compare commits

...

104 Commits

Author SHA1 Message Date
Hugo Thunnissen 4d9907fedc Strip partially typed attributes from resolvecontext when suggesting attributes
ci/woodpecker/push/woodpecker Pipeline failed Details
2 months ago
Hugo Thunnissen 797efe9530 Only attempt to return completion kind when it can be found
When completions aren't found this is probably the result of a bug, but errors
in corfu's hooks are a pain for users so it is better to handle them gracefully
2 months ago
Hugo Thunnissen 18bc2e83ad Convert names to alist for use as completing-read collection
ci/woodpecker/push/woodpecker Pipeline failed Details
Fixes bug in `phpinspect-fix-imports'
2 months ago
Hugo Thunnissen 712268e4ec Fix bug in autoloader that caused registration of only one FQN per type
ci/woodpecker/push/woodpecker Pipeline failed Details
2 months ago
Hugo Thunnissen 42c7bd2715 Handle unsupported annotation styles gracefully
ci/woodpecker/push/woodpecker Pipeline failed Details
2 months ago
Hugo Thunnissen 0509d8c669 Ignore nil-value completions
ci/woodpecker/push/woodpecker Pipeline failed Details
There is probably a bug in an undedlying completion strategy that is causing
these nil-values. But this function should be able to deal with that regardless
as completion-strategies may not always be stable. Especially if user-defined
strategies ever become a thing.
2 months ago
Hugo Thunnissen ea133c2044 Fix bug in `phpinspect-splayt-find-all-between' that impacted import indexation
ci/woodpecker/push/woodpecker Pipeline failed Details
2 months ago
Hugo Thunnissen e436b3fae0 Ignore non-existant directories in psr4 autoload config
ci/woodpecker/push/woodpecker Pipeline failed Details
3 months ago
Hugo Thunnissen 7f2baf2c68 Make phpinspect--match-sequence return nil unless entire sequence matches
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 8c23a3fc2d Use phpinspect--class-name, as index can be unset for live edited classes
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen d3eec5c0bd Temporary fix for error upon indexation of anonymous functions 9 months ago
Hugo Thunnissen a40731aa3d Catch `phpinspect-parse-interrupted' in function `phpinspect-complete-at-point' 9 months ago
Hugo Thunnissen 44e109a5ac Add #'phpinspect-fix-imports to mode keymap 9 months ago
Hugo Thunnissen 77ea6ae9e6 Add headers to new .el files + delete compile script
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 6b832b521b Prefix unused lexical variable in test with _
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen b1085763df Use `defvar' instead of `defconst' for variables that are actually changed during runtime 9 months ago
Hugo Thunnissen 3e28231d03 Remove unnecessary macros + use `let' to set PLACE in iterative macros 9 months ago
Hugo Thunnissen da2570d355 Update README with install/build instructions
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 1031cd929d Handle quitting more gracefully in worker and pipeline threads
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 54679b616f Use `phpinspect-message' in stead of `message' 9 months ago
Hugo Thunnissen 81919175ca Implement stub index for builtin functions and types
Misc:
- Removed Cask in favor of dependency install script
- Rework makefile to provide simple build/install process
9 months ago
Hugo Thunnissen 9f7026455a Generate builtin stubs (+ add script to do so)
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 5eae689f09 Add separate interactive function to refresh project autoloader
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen e9d547af07 Add compilation/benchmarks section to README
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 52e1254001 Use compile scripts in makefile + add bytecomp step to native comp script
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 4a75cd350c Insert closing parenthesis when function does not take any arguments
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen d806c4ef4a Use class-keyword handler regexp when extracting class name from declaration
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen b82c786346 Fix native compilation warnings
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 2d29bce498 Apply overall code quality improvements
ci/woodpecker/push/woodpecker Pipeline failed Details
Even benchmarks and tests now compile without warnings or errors :)

This includes patches from Stefan Monnier:
- https://lists.gnu.org/archive/html/emacs-devel/2023-08/msg00548.html
9 months ago
Hugo Thunnissen 20ec37481a Fix compilation of benchmarks and tests
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen b102e037a8 Add copyright headers to benchmark files to fix(?) ELPA tarball build
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen a99f73b83d Use rear pointer instead of nconc to append to end of list
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 16aa30d04f Add ELPA generated files to .gitignore
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 8cfc48348f Run relint and fix detected regexp issues
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen ad4b1f25a6 Update copyright statements and apply some code style improvements
ci/woodpecker/push/woodpecker Pipeline failed Details
As suggested by Stefan Monniers patch:
 - https://mail.gnu.org/archive/html/emacs-devel/2023-08/msg00367.html
9 months ago
Hugo Thunnissen daf070a083 Add missing struct definition
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 9d697550f7 Fix oopsie in compile script 9 months ago
Hugo Thunnissen 84ddaf1dc2 Fix all byte compilation warnings and errors (for real this time (probably))
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen f1e4a5be7d Remove debug statement for company backend
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen f6cc199886 When debugging, forward worker errors to main thread 9 months ago
Hugo Thunnissen f9f12590e8 Handle cases where declarations do not contain a name 9 months ago
Hugo Thunnissen 71531b7996 Define free variables + use condition-case-unless-debug 9 months ago
Hugo Thunnissen 2ff9919e44 Add debug statements + use condition-case-unless-debug for token indexation 9 months ago
Hugo Thunnissen 6e5e2e3a07 Exclude comments from eldoc statement 9 months ago
Hugo Thunnissen 633b08809e Only retrieve/update class when a name has been declared for it 9 months ago
Hugo Thunnissen 8b6dc2eb38 Catch composer json format errors and notify user 9 months ago
Hugo Thunnissen 7c76cbcc54 Use rear pointer to append parsed tokens
ci/woodpecker/push/woodpecker Pipeline failed Details
9 months ago
Hugo Thunnissen 68c826243c Keep index synchronized with buffer state 9 months ago
Hugo Thunnissen 3175d9a6ac Fix typo
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 9513cbc917 Update README
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen db3ec3b67d Add custom variables for worker and pipeline pause time
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 5fe0b7bdc5 Add .cask directory to gitignore
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen e067a9e9dd Remove obsolete function and add some documentation to mode doc string
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 04606a4756 Fix test
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen e4b62c0230 Implement completion-at-point function
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen fbfcf4f928 Fix byte compilation warnings
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen e35caa7e15 Fix a variety of bugs
ci/woodpecker/push/woodpecker Pipeline failed Details
- phpinspect--index-tokens no longer errors upon unexpected return annotation
values
- phpinspect-fixt-imports now also fixes imports outside of classes
- Functions are no longer included in statements when deriving types
10 months ago
Hugo Thunnissen 55413ea9fb Implement basic support for function indexation and include dirs
ci/woodpecker/push/woodpecker Pipeline failed Details
Does not yet include support for imported namespaced functions
10 months ago
Hugo Thunnissen 05ca0ace20 Fix all remaining byte compiler warnings
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen d86ef5756b Remove `phpinspect-define-pipeline-step' in favor of direct fun call 10 months ago
Hugo Thunnissen c20df819b8 Give `phpinspect-buffer' responsibility over buffer indexation
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen f5cc681105 Fix let parenthesis in test-buffer.el 10 months ago
Hugo Thunnissen d51137e58e Remove faulty edit tracker code based on wrong deduction 10 months ago
Hugo Thunnissen 9b82c0d0f6 Reimplement `phpinspect-fix-imports' using metadata objects 10 months ago
Hugo Thunnissen 135263c533 Add tests for incremental parsing + fix parser bugs that came to light 10 months ago
Hugo Thunnissen f2ece03f2a Add factilities to filter logs from different modules 10 months ago
Hugo Thunnissen db370623da Implement "files" autoload strategy 10 months ago
Hugo Thunnissen 94d5b75455 Add `phpinspect-pipeline-pause-time'
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen ea7795c76e Remove commented code
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen f003b6a279 Make project indexation asynchronous using `phpinspect-pipeline'
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 2fd91898a3 Add tests for pipeline and make API more ergonomic
- Added `phpinspect-pipeline-emit-all' to emit multiple values at once
- Added :async parameter to `phpinspect-pipeline'
- Improved error handling
- Only execute seed form once and require it to return a list
10 months ago
Hugo Thunnissen 6678ba20c6 Implement async processing pipeline 10 months ago
Hugo Thunnissen 2d2f9912c1 Wrap queue items in a queue object
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen ce995f2bc4 Remove unused variables 10 months ago
Hugo Thunnissen 9f7931a7b6 Rework parser for ahead of time byte compilation
ci/woodpecker/push/woodpecker Pipeline failed Details
This commit does away with the JIT approach that byte compiles parser functions
on the go. Some workarounds have been implemented to inline handler functions
while still allowing them to call the parser functions they are used in.
10 months ago
Hugo Thunnissen 2099abced8 Add Cask configuration and fix some compilation warnings
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 23245d0158 Fix some compilation warnings
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 2049121810 Make edit delta lookup inclusive of current point
ci/woodpecker/push/woodpecker Pipeline failed Details
10 months ago
Hugo Thunnissen 4c5a70d4af Make buffer re-parse non-interruptible + add interactive function to view tree 10 months ago
Hugo Thunnissen 9a25959aad Use metadata tree instead of hash table for token lookup 10 months ago
Hugo Thunnissen b68baaec83 Make parse context cancellable and restore state after interrupt 10 months ago
Hugo Thunnissen 111fa2f4b5 Fix bugs in splay tree "find" functions 10 months ago
Hugo Thunnissen 389e77eb8b Expand existing overlay when possible 10 months ago
Hugo Thunnissen 0596bc52bf Optimize splay tree and use it to store token's children
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen ab6954faf5 Retrieve and wrap metadata using the correct overlay for region
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 6c767fc877 Implement eldoc for object attributes 10 months ago
Hugo Thunnissen e270729e14 Implement splay tree for overlay storage/lookup
ci/woodpecker/push/woodpecker Pipeline was successful Details
This makes repeated overlay lookups during incremental parsing or buffer
analysis more efficient.
10 months ago
Hugo Thunnissen 55a24065a6 Remove obsolete comment (incremental parsing has been implemented)
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen c35c00ceff Implement strategy pattern for completion backend
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 75562aab35 Add some tests for edit tracker + patch newly discovered bugs 10 months ago
Hugo Thunnissen d1d34a4249 Move more functionalities from main file to separate modules 10 months ago
Hugo Thunnissen 1f145665ef Exclude "return" from resolvecontext subject + count comma at point for eldoc arg number
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 5fab07b426 Keep track of multi-call edits of the same region 10 months ago
Hugo Thunnissen 43310092ad Clear tree and edit tracker when reparsing (to ensure full reparse) 10 months ago
Hugo Thunnissen 8dd9bb07e4 Increase phpinspect-bmap-last-token-before-point backward search limit to 100 10 months ago
Hugo Thunnissen bb04e9a0f8 Implement strategy pattern for phpinspect-eldoc-function 10 months ago
Hugo Thunnissen 1ec0e0cfa2 Limit token lookback range and start completion from the first non-blank character
ci/woodpecker/push/woodpecker Pipeline was successful Details
When editing files where tokens occur sparingly, like in HTML templates, looking
back for the last token that occured is very expensive and never useful.
10 months ago
Hugo Thunnissen 281c5e4ae6 Remove some overly verbose logging
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 9d6ce5726d Use `phpinspect-edtrack-original-position-at-point' for edit end determination 10 months ago
Hugo Thunnissen 5548734ef7 Implement parser interruption on user input
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 91e24b97d4 Make bmap-token-meta error on unexpected input
Searching for an object that doesn't exist as a token can be really expensive,
so it's better to error on unexpected input and fix code on the calling side.
10 months ago
Hugo Thunnissen 6627f6f76f Remove commented parser code
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago
Hugo Thunnissen 7f76ba4c11 Fix some bugs introduced by the incremental parsing feature
ci/woodpecker/push/woodpecker Pipeline was successful Details
Among other things:

- use-keyword parser handler result being registered for two positions due to
wrong use of "root" parser parameter.
- phpinspect-fix-imports was broken
10 months ago
Hugo Thunnissen ad5ede01ad Implement Incremental Parsing
ci/woodpecker/push/woodpecker Pipeline was successful Details
10 months ago

10
.gitignore vendored

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

@ -0,0 +1,52 @@
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,8 +1,46 @@
# 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)).
WIP. More documentation is in the making.
## 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`.
## Example config
## 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:
```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
```elisp
;;;###autoload
@ -27,10 +65,12 @@ WIP. More documentation is in the making.
(add-hook 'php-mode-hook #'my-php-personal-hook)
```
## Install
## Install from git
```bash
git clone https://git.snorba.art/hugo/phpinspect.el ~/projects/phpinspect.el
cd ~/projects/phpinspect.el
make
```
```elisp
@ -38,12 +78,67 @@ git clone https://git.snorba.art/hugo/phpinspect.el ~/projects/phpinspect.el
(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
emacs -L ./ -batch -l ert -l ./phpinspect.el -l ./test/phpinspect-test.el -f ert-run-tests-batch-and-exit
make test
# or:
emacs -L ./ -batch -l ert -l ./test/phpinspect-test.el -f ert-run-tests-batch-and-exit
```

File diff suppressed because it is too large Load Diff

@ -0,0 +1,53 @@
;;; 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)))

@ -0,0 +1,137 @@
;;; 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)))

@ -0,0 +1,103 @@
;;; 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)))

@ -0,0 +1,51 @@
;;; 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,6 +1,6 @@
;;; phpinspect-autoload.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -24,14 +24,22 @@
;;; Code:
(require 'cl-lib)
(require 'phpinspect-project)
(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
@ -42,23 +50,34 @@
(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-classmap
(:constructor phpinspect-make-classmap-generated))
(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))
(project nil
:type phpinspect-project
:documentation "The project that this autoloader can find files for")
(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
@ -74,134 +93,196 @@
qualified names congruent with a bareword type name. Keyed by
bareword typenames."))
(defun phpinspect-make-autoload-definition-closure (project-root fs typehash)
"Create a closure that can be used to `maphash' the autoload section of a composer-json."
(lambda (type prefixes)
(let ((strategy))
(cond
((string= "psr-0" type)
(maphash
(lambda (prefix directory-paths)
(when (stringp directory-paths) (setq directory-paths (list directory-paths)))
(setq strategy (phpinspect-make-psr0-generated :prefix prefix))
(dolist (path directory-paths)
(push (concat project-root "/" path)
(phpinspect-psr0-directories strategy))))
prefixes))
((string= "psr-4" type)
(maphash
(lambda (prefix directory-paths)
(when (stringp directory-paths) (setq directory-paths (list directory-paths)))
(setq strategy (phpinspect-make-psr4-generated :prefix prefix))
(dolist (path directory-paths)
(push (concat project-root "/" path)
(phpinspect-psr4-directories strategy))))
prefixes))
(t (phpinspect--log "Unsupported autoload strategy \"%s\" encountered" type)))
(when strategy
(phpinspect-al-strategy-fill-typehash strategy fs typehash)))))
(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))))
(cl-defmethod phpinspect-autoloader-refresh ((autoloader phpinspect-autoloader))
"Refresh autoload definitions by reading composer.json files
from the project and vendor folders."
(let* ((project-root (phpinspect-project-root (phpinspect-autoloader-project autoloader)))
(fs (phpinspect-project-fs (phpinspect-autoloader-project autoloader)))
(vendor-dir (concat project-root "/vendor"))
(composer-json-path (concat project-root "/composer.json"))
(composer-json)
(project-autoload )
(type-name-fqn-bags (make-hash-table :test 'eq :size 3000 :rehash-size 3000))
(own-types (make-hash-table :test 'eq :size 10000 :rehash-size 10000))
(types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)))
(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))))))))
(when (phpinspect-fs-file-exists-p fs composer-json-path)
(setq composer-json (phpinspect--read-json-file fs composer-json-path))
(if (hash-table-p composer-json)
(setq project-autoload (gethash "autoload" composer-json))
(phpinspect--log "Error: Parsing %s did not return a hashmap."
composer-json-path)))
(when project-autoload
(maphash (phpinspect-make-autoload-definition-closure project-root fs own-types)
project-autoload)
(maphash (phpinspect-make-autoload-definition-closure project-root fs types)
project-autoload))
(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 (concat dependency-dir "/composer.json")))
(let* ((dependency-json (phpinspect--read-json-file
fs
(concat dependency-dir "/composer.json")))
(dependency-autoload (gethash "autoload" dependency-json)))
(when dependency-autoload
(maphash (phpinspect-make-autoload-definition-closure
dependency-dir fs types)
dependency-autoload))))))))
(maphash (lambda (type-fqn _)
(let* ((type-name (phpinspect-intern-name
(car (last (split-string (symbol-name type-fqn) "\\\\")))))
(bag (gethash type-name type-name-fqn-bags)))
(push type-fqn bag)
(puthash type-name bag type-name-fqn-bags)))
types)
(setf (phpinspect-autoloader-own-types autoloader) own-types)
(setf (phpinspect-autoloader-types autoloader) types)
(setf (phpinspect-autoloader-type-name-fqn-bags autoloader)
type-name-fqn-bags)))
(phpinspect-fs-file-exists-p fs cj-path))
(push `(vendor . ,cj-path) files))))))
files))
(cl-defmethod phpinspect-autoloader-resolve ((autoloader phpinspect-autoloader)
typename-symbol)
(or (gethash typename-symbol (phpinspect-autoloader-own-types autoloader))
(gethash typename-symbol (phpinspect-autoloader-types autoloader))))
(cl-defgeneric phpinspect-al-strategy-fill-typehash (strategy fs typehash)
"Make STRATEGY return a map with type names as keys and the
paths to the files they are defined in as values.")
(defsubst phpinspect-filename-to-typename (dir filename &optional prefix)
(phpinspect-intern-name
(replace-regexp-in-string
"[\\\\]+"
"\\\\"
(concat "\\"
(or prefix "")
(replace-regexp-in-string
"/" "\\\\"
(string-remove-suffix
".php"
(string-remove-prefix dir filename)))))))
(cl-defmethod phpinspect-al-strategy-fill-typehash ((strategy phpinspect-psr0)
fs
typehash)
(dolist (dir (phpinspect-psr0-directories strategy))
(dolist (file (phpinspect-fs-directory-files-recursively fs dir "\\.php$"))
(puthash (phpinspect-filename-to-typename dir file) file typehash))))
(cl-defmethod phpinspect-al-strategy-fill-typehash ((strategy phpinspect-psr4)
fs
typehash)
(let ((prefix (phpinspect-psr4-prefix strategy)))
(dolist (dir (phpinspect-psr4-directories strategy))
(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$"))
(puthash (phpinspect-filename-to-typename dir file prefix) file typehash)))))
(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

@ -0,0 +1,288 @@
;;; 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,6 +1,6 @@
;;; phpinspect-buffer.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -23,72 +23,472 @@
;;; 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'.")
(defsubst phpinspect-make-region (start end)
(list start end))
(defalias 'phpinspect-region-start #'car)
(defalias 'phpinspect-region-end #'cadr)
(defsubst phpinspect-region-size (region)
(- (phpinspect-region-end region) (phpinspect-region-start region)))
(defsubst phpinspect-region> (reg1 reg2)
(> (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defsubst phpinspect-region< (reg1 reg2)
(< (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(cl-defstruct (phpinspect-buffer (:constructor phpinspect-make-buffer))
"An object containing phpinspect related metadata linked to an
emacs buffer."
(buffer nil
:type buffer
:documentation "The underlying emacs buffer")
(location-map (make-hash-table :test 'eq :size 400 :rehash-size 400)
:type hash-table
:documentation
"A map that lets us look up the character
positions of a token within this buffer.")
:documentation "The associated emacs buffer")
(tree nil
:type list
:documentation
"An instance of a token tree as returned by
`phpinspect--index-tokens'. Meant to be eventually consistent
with the contents of the buffer."))
"Parsed token tree that resulted from last parse")
(map nil
:type phpinspect-bmap)
(-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))
(cl-defmethod phpinspect-buffer-parse ((buffer phpinspect-buffer))
(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."
(with-current-buffer (phpinspect-buffer-buffer buffer)
(setf (phpinspect-buffer-location-map buffer)
(make-hash-table :test 'eq
:size 400
:rehash-size 400))
(let ((tree))
(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))
(let ((tree (phpinspect-parse-current-buffer)))
(setf (phpinspect-buffer-tree buffer) tree)
tree)))
(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)))
(cl-defmethod phpinspect-buffer-token-location ((buffer phpinspect-buffer) token)
(gethash token (phpinspect-buffer-location-map buffer)))
(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)
(let ((tokens))
(maphash
(lambda (token region)
(when (and (<= (phpinspect-region-start region) point)
(>= (phpinspect-region-end region) point))
(push token tokens)))
(phpinspect-buffer-location-map buffer))
(sort tokens (lambda (tok1 tok2)
(phpinspect-region< (phpinspect-buffer-token-location tok1)
(phpinspect-buffer-token-location tok2))))))
(phpinspect-bmap-tokens-overlapping (phpinspect-buffer-map buffer) point))
(cl-defmethod phpinspect-buffer-token-meta ((buffer phpinspect-buffer) token)
(phpinspect-bmap-token-meta (phpinspect-buffer-map buffer) token))
(cl-defmethod phpinspect-buffer-location-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,6 +1,6 @@
;;; phpinspect.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -25,39 +25,266 @@
(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."))
(cl-defgeneric phpinspect--cache-getproject
((cache phpinspect--cache) (project-name string))
"Get project by PROJECT-NAME that is located in CACHE.")
(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)))
(cl-defmethod phpinspect--cache-getproject
(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))
(gethash project-root (phpinspect--cache-projects cache)))
(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-getproject cache project-root)))
(let ((project (phpinspect--cache-get-project cache project-root)))
(unless project
(setq project (puthash project-root
(phpinspect--make-project
:fs (phpinspect-make-fs)
:root project-root
:worker (phpinspect-make-dynamic-worker))
(phpinspect--cache-projects cache)))
(let ((autoload (phpinspect-make-autoloader :project project)))
(setf (phpinspect-project-autoload project) autoload)
(phpinspect-autoloader-refresh autoload)))
(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))
(provide 'phpinspect-cache)
(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)

@ -0,0 +1,70 @@
;;; 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

@ -0,0 +1,78 @@
;;; 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,6 +1,6 @@
;;; phpinspect-class.el --- PHP parsing module -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -24,53 +24,56 @@
;;; Code:
(require 'phpinspect-type)
(require 'phpinspect-class-struct)
(cl-defstruct (phpinspect--class (:constructor phpinspect--make-class-generated))
(project nil
:type phpinspect-project
:documentaton
"The project that this class belongs to")
(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.")
(variables nil
:type list
:documentation
"Variables that belong to this class.")
(extended-classes (make-hash-table :test 'eq)
:type hash-table
:documentation
"All extended/implemented classes.")
(subscriptions nil
:type list
:documentation
"A list of subscription functions that should be
called whenever anything about this class is
updated")
(initial-index nil
:type bool
:documentation
"A boolean indicating whether or not this class
has been indexed yet."))
(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 (phpinspect--class-subscriptions 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)))
(setf (phpinspect--class-initial-index class) t)
(setf (phpinspect--class-index class) index)
(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))
@ -78,28 +81,59 @@
(phpinspect--class-update-static-method class method))
(setf (phpinspect--class-variables class)
(alist-get 'variables index))
(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)))
(setf (phpinspect--class-extended-classes class)
(seq-filter
#'phpinspect--class-p
(mapcar
(lambda (class-name)
(phpinspect-project-get-class-create (phpinspect--class-project class)
class-name))
`(,@(alist-get 'implements index) ,@(alist-get 'extends index)))))
(cl-defmethod phpinspect--class-get-static-method ((class phpinspect--class) (method-name (head phpinspect-name)))
(gethash method-name (phpinspect--class-static-methods class)))
(dolist (extended (phpinspect--class-extended-classes class))
(phpinspect--class-incorporate class extended)
(phpinspect--class-subscribe class extended))
(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)))))
(phpinspect--class-trigger-update class))
(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-get-method ((class phpinspect--class) method-name)
(gethash method-name (phpinspect--class-methods 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-static-method ((class phpinspect--class) method-name)
(gethash method-name (phpinspect--class-static-methods 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)
@ -118,28 +152,35 @@
(cl-defmethod phpinspect--class-set-method ((class phpinspect--class)
(method phpinspect--function))
(phpinspect--log "Adding method by name %s to class"
(phpinspect--function-name method))
(phpinspect--add-method-copy-to-map
(phpinspect--class-methods class)
(alist-get 'class-name (phpinspect--class-index class))
method))
(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)
(alist-get 'class-name (phpinspect--class-index class))
method))
(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 symbol))
((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 symbol))
((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))))
@ -153,7 +194,8 @@
(cl-defmethod phpinspect--merge-method ((class-name phpinspect--type)
(existing phpinspect--function)
(method phpinspect--function))
(method phpinspect--function)
&optional extended)
(let ((new-return-type (phpinspect--resolve-late-static-binding
(phpinspect--function-return-type method)
class-name)))
@ -162,48 +204,60 @@
(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))
(let ((existing (gethash (phpinspect--function-name-symbol method)
(phpinspect--class-static-methods class))))
(if existing
(phpinspect--merge-method
(alist-get 'class-name (phpinspect--class-index class))
existing method)
(phpinspect--class-set-static-method class method))))
(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))
(let* ((existing (gethash (phpinspect--function-name-symbol method)
(phpinspect--class-methods class))))
(if existing
(phpinspect--merge-method
(alist-get 'class-name (phpinspect--class-index class))
existing method)
(phpinspect--class-set-method class method))))
(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))
(dolist (method (phpinspect--class-get-method-list other-class))
(phpinspect--class-update-method class method))
(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)))
(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))
(let ((update-function
(lambda (new-class)
(phpinspect--class-incorporate class new-class)
(phpinspect--class-trigger-update class))))
(push update-function (phpinspect--class-subscriptions subscription-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

@ -0,0 +1,319 @@
;;; 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)

@ -0,0 +1,235 @@
;;; 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

@ -0,0 +1,283 @@
;;; 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,6 +1,6 @@
;;; phpinspect-fs.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -41,9 +41,8 @@
(defalias 'phpinspect-virtual-file-modification-time #'cadr)
(defalias 'phpinspect-virtual-file-contents #'car)
(cl-defmethod phpinspect-virtual-fs-set-file ((fs phpinspect-virtual-fs)
path
contents)
(defun phpinspect-virtual-fs-set-file (fs path contents)
(declare (indent defun))
(puthash path (phpinspect-make-virtual-file contents)
(phpinspect-virtual-fs-files fs)))
@ -61,13 +60,13 @@ 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)
(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)
(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)
@ -86,14 +85,15 @@ thread, PREFER-ASYNC has no effect.")
(when file
(phpinspect-virtual-file-modification-time file))))
(cl-defmethod phpinspect-fs-file-modification-time ((fs phpinspect-fs) 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.
"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
@ -127,7 +127,7 @@ only be the result of a logic error."
(condition-wait condition)
(when err (error err)))))
(cl-defmethod phpinspect-fs-insert-file-contents ((fs phpinspect-fs) file &optional prefer-async)
(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)
@ -138,7 +138,7 @@ only be the result of a logic error."
(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)
(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)
@ -154,7 +154,7 @@ only be the result of a logic error."
(phpinspect-virtual-fs-files fs))
files))
(cl-defmethod phpinspect-fs-directory-files-recursively ((fs phpinspect-fs) directory &optional match)
(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

@ -1,6 +1,6 @@
; phpinspect-imports.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -25,17 +25,26 @@
;;; Code:
(require 'phpinspect-parser)
(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-add-use (fqn buffer &optional namespace-token)
(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
@ -44,112 +53,113 @@ buffer position to insert the use statement at."
(when (string-match "^\\\\" fqn)
(setq fqn (string-trim-left fqn "\\\\")))
(if namespace-token
(let* ((region (gethash
namespace-token (phpinspect-buffer-location-map buffer)))
(existing-use (seq-find #'phpinspect-use-p
(phpinspect-namespace-body namespace-token)))
(namespace-block (phpinspect-namespace-block namespace-token)))
(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-region-start
(phpinspect-buffer-token-location buffer existing-use))
(format "use %s;%c" fqn ?\n))
(phpinspect-meta-start existing-use) (format "use %s;%c" fqn ?\n))
(if namespace-block
(phpinspect-insert-at-point
(+ 1 (phpinspect-region-start
(phpinspect-buffer-token-location buffer namespace-block)))
(+ 1 (phpinspect-meta-start namespace-block))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-insert-at-point
(phpinspect-region-end
(phpinspect-buffer-token-location
buffer (seq-find #'phpinspect-terminator-p namespace-token)))
(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 (seq-find #'phpinspect-use-p
(phpinspect-buffer-tree buffer))))
(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-region-start
(phpinspect-buffer-token-location buffer existing-use))
(phpinspect-meta-start existing-use)
(format "use %s;%c" fqn ?\n))
(let ((first-token (cadr (phpinspect-buffer-tree buffer))))
(if (and (phpinspect-word-p first-token)
(string= "declare" (cadr first-token)))
(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-region-end
(phpinspect-buffer-token-location
buffer (seq-find #'phpinspect-terminator-p (phpinspect-buffer-tree buffer))))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-meta-end token-after) (format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
(phpinspect-insert-at-point
(phpinspect-region-start
(phpinspect-buffer-token-location buffer first-token))
(phpinspect-meta-start 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))
(fqn-bags (phpinspect-autoloader-type-name-fqn-bags autoloader)))
(let ((fqns (gethash typename fqn-bags)))
(cond ((= 1 (length fqns))
(phpinspect-add-use (symbol-name (car fqns)) buffer namespace-token))
((> (length fqns) 1)
(phpinspect-add-use (completing-read "Class: " fqns)
buffer namespace-token))
(t (message "No import found for type %s" typename))))))
(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 "\\\\?[^\\\\]+"))
(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* ((tree (phpinspect-buffer-parse phpinspect-current-buffer))
(location-map (phpinspect-buffer-location-map phpinspect-current-buffer))
(let* ((buffer phpinspect-current-buffer)
(tree (phpinspect-buffer-parse buffer))
(index (phpinspect--index-tokens
tree nil (lambda (token) (gethash token location-map))))
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))))
(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))
(region (alist-get 'location class)))
(dolist (type used-types)
(let ((namespace
(seq-find #'phpinspect-namespace-p
(phpinspect-buffer-tokens-enclosing-point
phpinspect-current-buffer (phpinspect-region-start region)))))
;; Add use statements for types that aren't imported.
(unless (or (or (alist-get type class-imports)
(alist-get type imports))
(gethash (phpinspect-intern-name
(concat (phpinspect-namespace-part-of-typename
(phpinspect--type-name (alist-get 'class-name class)))
"\\"
(symbol-name type)))
(phpinspect-autoloader-types
(phpinspect-project-autoload project))))
(phpinspect-add-use-interactive
type phpinspect-current-buffer project namespace)
;; Buffer has been modified by adding type, update tree +
;; location map. This is not optimal but will have to do until
;; partial parsing is implemented.
;;
;; Note: this basically implements a bug where the locations
;; of classes are no longer congruent with their location in
;; the buffer's code. In files that contain multiple namespace
;; blocks this could cause problems as a namespace may grow by
;; added import statements and start envelopping the classes
;; below it.
(phpinspect-buffer-parse phpinspect-current-buffer)))))))))
(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)

@ -1,6 +1,6 @@
;;; phpinspect-index.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -25,8 +25,9 @@
(require 'cl-lib)
(require 'phpinspect-util)
(require 'phpinspect-project)
(require 'phpinspect-type)
(require 'phpinspect-token-predicates)
(require 'phpinspect-parser)
(defun phpinspect--function-from-scope (scope)
(cond ((and (phpinspect-static-p (cadr scope))
@ -54,21 +55,53 @@
(nreverse arg-index)))
(defsubst phpinspect--should-prefer-return-annotation (type)
"When the return annotation should be preferred over typehint of TYPE, if available."
"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-from-scope (type-resolver scope comment-before &optional add-used-types)
(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))
(type (if (phpinspect-word-p (car (last declaration)))
(funcall type-resolver
(phpinspect--make-type :name (cadar (last declaration)))))))
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.
@ -80,11 +113,15 @@ function (think \"new\" statements, return types etc.)."
(phpinspect--log "found return annotation %s in %s when type is %s"
return-annotation-type comment-before type)
(when (string-suffix-p "[]" return-annotation-type)
(setq is-collection t)
(setq return-annotation-type (string-trim-right return-annotation-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))))
@ -101,27 +138,30 @@ function (think \"new\" statements, return types etc.)."
(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))
:name (cadadr (cdr declaration))
:token php-func
:name (concat (if namespace (concat namespace "\\") "") name)
:return-type (or type phpinspect--null-type)
:arguments (phpinspect--index-function-arg-list
type-resolver
(phpinspect-function-argument-list php-func)
add-used-types))))
: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))
:name (cadr (cadr (cadr 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--index-variable-from-scope (type-resolver scope comment-before)
"Index the variable inside `scope`."
(let* ((var-annotations (phpinspect--var-annotations-from-token comment-before))
(variable-name (cadr (cadr scope)))
(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)
@ -129,24 +169,27 @@ function (think \"new\" statements, return types etc.)."
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
:name variable-name
;; 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))
(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-handler-regexp 'class-keyword))
(concat (cadr word) " ")))))
(cadr class-token))))
(cadr subtoken)))
(defsubst phpinspect--index-method-annotations (type-resolver comment)
(let ((annotations (seq-filter #'phpinspect-method-annotation-p comment))
@ -175,7 +218,6 @@ function (think \"new\" statements, return types etc.)."
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")
@ -186,7 +228,7 @@ function (think \"new\" statements, return types etc.)."
(constants)
(extends)
(implements)
(class-name (phpinspect--get-class-name-from-token class))
(class-name)
;; Keep track of encountered comments to be able to use type
;; annotations.
(comment-before)
@ -199,30 +241,9 @@ function (think \"new\" statements, return types etc.)."
(nconc used-types additional-used-types)
(setq used-types additional-used-types))))
;; Find out what the class extends or implements
(let ((enc-extends nil)
(enc-implements nil))
(dolist (word (cadr class))
(if (phpinspect-word-p word)
(cond ((string= (cadr word) "extends")
(phpinspect--log "Class %s extends other classes" class-name)
(setq enc-extends t))
((string= (cadr word) "implements")
(setq enc-extends nil)
(phpinspect--log "Class %s implements in interface" class-name)
(setq enc-implements t))
(t
(phpinspect--log "Calling Resolver from index-class on %s" (cadr word))
(cond (enc-extends
(push (funcall type-resolver (phpinspect--make-type
:name (cadr word)))
extends)
(push (cadr word) used-types))
(enc-implements
(push (funcall type-resolver (phpinspect--make-type
:name (cadr word)))
implements)
(push (cadr word) 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)
@ -248,7 +269,8 @@ function (think \"new\" statements, return types etc.)."
(push (phpinspect--index-variable-from-scope type-resolver
(list (car token)
(cadadr token))
comment-before)
comment-before
'static)
static-variables))))
(t
(phpinspect--log "comment-before is: %s" comment-before)
@ -320,21 +342,22 @@ function (think \"new\" statements, return types etc.)."
(setq methods
(nconc methods (phpinspect--index-method-annotations type-resolver doc-block))))
(let ((class-name (funcall type-resolver (phpinspect--make-type :name class-name))))
`(,class-name .
(phpinspect--indexed-class
(class-name . ,class-name)
(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=))))))))
`(,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.
@ -344,7 +367,7 @@ Accounts for namespaces that are defined with '{}' blocks."
(cdr namespace)))
(defun phpinspect--index-classes-in-tokens
(imports tokens type-resolver-factory location-resolver &optional namespace indexed)
(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"
@ -361,49 +384,63 @@ NAMESPACE will be assumed the root namespace if not provided"
(setq comment-before nil))))
indexed))
(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--index-namespace (namespace type-resolver-factory location-resolver)
(phpinspect--index-classes-in-tokens
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
namespace
type-resolver-factory location-resolver (cadadr namespace) nil))
(let* (used-types
(index
`((classes . ,(phpinspect--index-classes-in-tokens
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
namespace
type-resolver-factory location-resolver (cadadr namespace)))
(functions . ,(phpinspect--index-functions-in-tokens
namespace
type-resolver-factory
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
(cadadr namespace)
(lambda (types) (setq used-types (nconc used-types types))))))))
(push `(used-types . ,used-types) index)))
(defun phpinspect--index-namespaces
(namespaces type-resolver-factory location-resolver &optional indexed)
(if namespaces
(progn
(push (phpinspect--index-namespace (pop namespaces)
type-resolver-factory
location-resolver)
indexed)
(phpinspect--index-namespaces namespaces type-resolver-factory
location-resolver indexed))
(apply #'append (nreverse indexed))))
(defun phpinspect--index-functions (&rest _args)
"TODO: implement function indexation. This is a stub function.")
(let ((namespace-index
(phpinspect--index-namespace
(pop namespaces) type-resolver-factory location-resolver)))
(if indexed
(progn
(nconc (alist-get 'used-types indexed)
(alist-get 'used-types namespace-index))
(nconc (alist-get 'classes indexed)
(alist-get 'classes namespace-index))
(nconc (alist-get 'functions indexed)
(alist-get 'functions namespace-index)))
(setq indexed namespace-index))
(phpinspect--index-namespaces
namespaces type-resolver-factory location-resolver indexed))
indexed))
(defun phpinspect--index-functions-in-tokens (tokens type-resolver-factory &optional imports namespace add-used-types)
"TODO: implement function indexation. This is a stub function."
(let ((type-resolver (funcall type-resolver-factory imports nil namespace))
comment-before functions)
(dolist (token tokens)
(cond ((phpinspect-comment-p token)
(setq comment-before token))
((phpinspect-function-p token)
(push (phpinspect--index-function-from-scope
type-resolver `(:public ,token) comment-before add-used-types
namespace)
functions))))
functions))
(defun phpinspect--find-used-types-in-tokens (tokens)
"Find usage of the \"new\" keyword in TOKENS.
Return value is a list of the types that are \"newed\"."
(let ((previous-tokens)
(used-types))
(let* ((previous-tokens)
(used-types (cons nil nil))
(used-types-rear used-types))
(while tokens
(let ((token (pop tokens))
(previous-token (car previous-tokens)))
@ -412,54 +449,67 @@ Return value is a list of the types that are \"newed\"."
(phpinspect-word-p token))
(let ((type (cadr token)))
(when (not (string-match-p "\\\\" type))
(push type used-types))))
(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))
(push type used-types))))
(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 (append (phpinspect--find-used-types-in-tokens (cdr list))
used-types)))))
(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 (append (phpinspect--find-used-types-in-tokens (cdr token))
used-types))))
(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)))
used-types))
(cdr used-types)))
(defun phpinspect--index-tokens (tokens &optional type-resolver-factory location-resolver)
"Index TOKENS as returned by `phpinspect--parse-current-buffer`."
(unless type-resolver-factory
(setq type-resolver-factory #'phpinspect--make-type-resolver))
(unless location-resolver
(setq location-resolver (lambda (_) (list 0 0))))
(let ((imports (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p tokens))))
`(phpinspect--root-index
(imports . ,imports)
,(append
(append '(classes)
(phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-p tokens)
type-resolver-factory
location-resolver)
(phpinspect--index-classes-in-tokens
imports tokens type-resolver-factory location-resolver)))
,(append '(used-types)
(phpinspect--find-used-types-in-tokens tokens))
(functions))
;; TODO: Implement function indexation
))
(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-create project class-fqn))))
(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"

@ -0,0 +1,242 @@
;;; 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

@ -0,0 +1,138 @@
;;; 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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,394 @@
;;; 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

@ -0,0 +1,95 @@
;;; 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,6 +1,6 @@
;;; phpinspect-project.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -23,47 +23,36 @@
;;; 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)
(cl-defstruct (phpinspect-project (:constructor phpinspect--make-project))
(class-index (make-hash-table :test 'eq :size 100 :rehash-size 40)
:type hash-table
:documentation
"A `hash-table` that contains all of the currently
indexed classes in the project")
(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 nil
: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."))
(cl-defgeneric phpinspect-project-add-class
((project phpinspect-project) (class (head phpinspect--indexed-class)))
"Add an indexed CLASS to PROJECT.")
(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."
@ -77,99 +66,192 @@ indexed by the absolute paths of the files they're watching."))
(cl-defmethod phpinspect-project-watch-file ((project phpinspect-project)
filepath
callback)
(let ((watcher (file-notify-add-watch filepath '(change) callback)))
(puthash filepath watcher (phpinspect-project-file-watchers project))))
(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)
(dolist (method methods)
(when (phpinspect--function-return-type method)
(phpinspect-project-enqueue-if-not-present
project
(phpinspect--function-return-type method)))))
(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)
(dolist (var variables)
(when (phpinspect--variable-type var)
(phpinspect-project-enqueue-if-not-present project (phpinspect--variable-type var)))))
(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))
(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)))
(phpinspect--log "Adding unpresent class %s to index queue" type)
(phpinspect-worker-enqueue (phpinspect-project-worker project)
(phpinspect-make-index-task project 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-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)))
(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)))
(dolist (indexed-class (alist-get 'classes (cdr index)))
(phpinspect-project-add-class project (cdr indexed-class))))
((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)
(dolist (import imports)
(when import
(phpinspect--log "Adding import to index queue: %s" import)
(phpinspect-project-enqueue-if-not-present project (cdr import)))))
(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-add-class
((project phpinspect-project) (indexed-class (head phpinspect--indexed-class)))
(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 :project project)))
(cl-defmethod phpinspect-project-delete-class ((project phpinspect-project) (class phpinspect--class))
(phpinspect-project-delete-class project (phpinspect--class-name 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-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))
(puthash (phpinspect--type-name-symbol class-fqn)
class
(phpinspect-project-class-index project)))
(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))
(let ((class (phpinspect--make-class-generated :project project)))
(phpinspect-project-set-class project class-fqn class)
class))
(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))
((project phpinspect-project) (class-fqn phpinspect--type) &optional no-enqueue)
(let ((class (phpinspect-project-get-class project class-fqn)))
(unless class
(setq class (phpinspect-project-create-class project class-fqn))
(phpinspect-project-enqueue-if-not-present project class-fqn))
(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."
(gethash (phpinspect--type-name-symbol class-fqn)
(phpinspect-project-class-index 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)
@ -179,7 +261,8 @@ 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-autoloader-refresh autoloader))
(phpinspect-project-edit project
(phpinspect-autoloader-refresh autoloader)))
(let* ((result (phpinspect-autoloader-resolve
autoloader (phpinspect--type-name-symbol type))))
(if (not result)
@ -198,9 +281,7 @@ before the search is executed."
(condition-case error
(let* ((file (phpinspect-project-get-type-filepath project type))
(visited-buffer (when file (find-buffer-visiting file)))
(new-index)
(class-index))
(visited-buffer (when file (find-buffer-visiting file))))
(when file
(if visited-buffer
(with-current-buffer visited-buffer (phpinspect-index-current-buffer))
@ -217,5 +298,107 @@ before the search is executed."
(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

@ -0,0 +1,129 @@
;;; 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

@ -0,0 +1,500 @@
;;; 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)

@ -0,0 +1,231 @@
;;; 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,6 +1,6 @@
;;; phpinspect-index.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -26,6 +26,9 @@
(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)
@ -34,9 +37,13 @@
(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)
@ -53,10 +60,11 @@
(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))))
@ -87,10 +95,11 @@
(imports . ,,(append '(list)
(mapcar #'phpinspect--serialize-import
(alist-get 'imports index))))
(classes ,,@(mapcar (lambda (cons-class)
`(list ,(phpinspect--serialize-type (car cons-class))
,(phpinspect--serialize-indexed-class (cdr cons-class))))
(alist-get 'classes index)))
(classes . ,(list
,@(mapcar (lambda (cons-class)
`(cons ,(phpinspect--serialize-type (car cons-class))
,(phpinspect--serialize-indexed-class (cdr cons-class))))
(alist-get 'classes index))))
(functions . ,,(append '(list)
(mapcar #'phpinspect--serialize-function
(alist-get 'functions index))))))

@ -0,0 +1,499 @@
;;; 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)

@ -0,0 +1,144 @@
;;; 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)

@ -0,0 +1,83 @@
;;; 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)

@ -0,0 +1,254 @@
;;; 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,6 +1,6 @@
;;; phpinspect-type.el --- Data structures that represent phpinspect types -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -24,6 +24,10 @@
;;; Code:
(require 'phpinspect-util)
(require 'phpinspect-token-predicates)
(eval-when-compile
(require 'phpinspect-parser))
(cl-defstruct (phpinspect--type
(:constructor phpinspect--make-type-generated)
@ -40,7 +44,8 @@
(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")
"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
@ -59,10 +64,10 @@
;; 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 attempt to resolve relatively.
;; 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"))
(defconst phpinspect-native-types
(defvar phpinspect-native-types
(phpinspect--make-types (mapcar (lambda (name) (concat "\\" name))
phpinspect-native-typenames)))
@ -70,11 +75,23 @@
(phpinspect--make-types '("\\array" "\\iterable" "\\SplObjectCollection" "\\mixed"))
"FQNs of types that should be treated as collecitons when inferring types.")
(defconst phpinspect--object-type (phpinspect--make-type :name "\\object" :fully-qualified t))
(defconst phpinspect--static-type (phpinspect--make-type :name "\\static" :fully-qualified t))
(defconst phpinspect--self-type (phpinspect--make-type :name "\\self" :fully-qualified t))
(defconst phpinspect--this-type (phpinspect--make-type :name "\\this" :fully-qualified t))
(defconst phpinspect--null-type (phpinspect--make-type :name "\\null" :fully-qualified t))
(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)))
@ -106,7 +123,7 @@ See https://wiki.php.net/rfc/static_return_type ."
(cl-defmethod phpinspect--type-name ((type phpinspect--type))
(symbol-name (phpinspect--type-name-symbol type)))
(phpinspect-name-string (phpinspect--type-name-symbol type)))
(defun phpinspect--get-bare-class-name-from-fqn (fqn)
(car (last (split-string fqn "\\\\"))))
@ -154,13 +171,27 @@ NAMESPACE may be nil, or a string with a namespace FQN."
(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
(if token-tree (or (phpinspect--find-innermost-incomplete-class token-tree)
(phpinspect--find-class-token token-tree))))
(inside-class-name (if inside-class (phpinspect--get-class-name-from-token
inside-class))))
(and token-tree (or (phpinspect--find-innermost-incomplete-class token-tree)
(phpinspect--find-class-token token-tree))))
(inside-class-name
(and inside-class (phpinspect--get-class-name-from-token inside-class))))
(lambda (type)
(phpinspect--type-resolve
types
@ -174,6 +205,11 @@ NAMESPACE may be nil, or a string with a namespace FQN."
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))
@ -186,6 +222,15 @@ NAMESPACE may be nil, or a string with a namespace FQN."
: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
@ -208,13 +253,10 @@ return type of the function."))
,@(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-name-obarray)))
(cl-defgeneric phpinspect--function-name ((func phpinspect--function)))
(cl-defmethod phpinspect--function-name ((func phpinspect--function))
(symbol-name (phpinspect--function-name-symbol func)))
(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."
@ -223,16 +265,100 @@ return type of the function."))
:documentation
"A string containing the name of the variable.")
(scope nil
:type phpinspect-scope
:documentation
"When the variable is an object attribute, this should
contain the scope of the variable as returned by
`phpinspect-parse-scope`")
`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,6 +1,6 @@
;;; phpinspect-util.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -23,15 +23,113 @@
;;; Code:
(defvar phpinspect-name-obarray (obarray-make)
"An obarray containing symbols for all encountered names in
PHP. Used to optimize string comparison.")
(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")
(defsubst phpinspect-intern-name (name)
(intern name phpinspect-name-obarray))
(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)
@ -46,21 +144,6 @@ PHP. Used to optimize string comparison.")
(push item new-plist))
(nreverse new-plist)))
(defun phpinspect-toggle-logging ()
(interactive)
(if (setq phpinspect--debug (not phpinspect--debug))
(message "Enabled phpinspect logging.")
(message "Disabled phpinspect logging.")))
(defsubst phpinspect--log (&rest args)
(when phpinspect--debug
(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") "]: "
(apply #'format args) "\n")))))
(cl-defstruct (phpinspect--pattern
(:constructor phpinspect--make-pattern-generated))
"An object that can be used to match lists to a given
@ -72,20 +155,22 @@ pattern. See `phpinspect--match-sequence'."
:type list
:documentation "The original code list used to create this pattern"))
(defsubst phpinspect--make-pattern (&rest pattern)
(phpinspect--make-pattern-generated
:matcher (apply #'phpinspect--match-sequence-lambda pattern)
:code 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))))
(defun phpinspect--match-sequence-lambda (&rest pattern)
(lambda (sequence)
(apply #'phpinspect--match-sequence sequence 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))
(defun phpinspect--match-sequence (sequence &rest pattern)
(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
@ -107,30 +192,57 @@ element at this position in SEQUENCE.
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))
(count 0)
(sequence-length (/ pattern-length 2))
(sequence-pos 0)
(sequence-length (/ pattern-length 2)))
(and (= sequence-length (length sequence))
(catch 'found
(while (< count pattern-length)
(let ((key (elt pattern count))
(value (elt pattern (+ count 1))))
(unless (keywordp key)
(error (format "Invalid, expected keyword, got %s" key)))
(cond ((eq key :m)
(unless (eq value '*)
(unless (equal value (elt sequence sequence-pos))
(throw 'found nil))))
((eq key :f)
(unless (funcall value (elt sequence sequence-pos))
(throw 'found nil)))
(t (error (format "Invalid keyword: %s" key))))
(setq count (+ count 2)
sequence-pos (+ sequence-pos 1))))
(throw 'found t)))))
(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)))
@ -146,7 +258,49 @@ it evaluates to a non-nil value."
:code (append (phpinspect--pattern-code pattern1)
(phpinspect--pattern-code pattern2)))))
(defun phpinspect--locate-dominating-project-file (start-file)
"Locate the first dominating file in `phpinspect-project-root-file-list`.
Starts looking at START-FILE and then recurses up the directory
hierarchy as long as no matching files are found. See also
`locate-dominating-file'."
(let ((dominating-file))
(seq-find (lambda (file)
(setq dominating-file (locate-dominating-file start-file file)))
phpinspect-project-root-file-list)
dominating-file))
(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,6 +1,6 @@
;;; phpinspect-worker.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; phpinspect-worker.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
@ -24,130 +24,27 @@
;;; Code:
(require 'cl-lib)
(require 'phpinspect-project)
(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-index-task
(:constructor phpinspect-make-index-task-generated))
"Represents an index task that can be executed by a `phpinspect-worker`."
(project nil
:type phpinspect-project
:documentation
"The project that the task should be executed for.")
(type nil
:type phpinspect--type
:documentation
"The type whose file should be indexed."))
(cl-defstruct (phpinspect-queue-item
(:constructor phpinspect-make-queue-item))
(next nil
:type phpinspect-queue-item
:documentation
"The next item in the queue")
(thing nil
:type any
:documentation
"The thing stored in the queue")
(previous nil
:type phpinspect-queue-item
:documentation
"The previous item in the queue")
(subscription nil
:type function
:read-only t
:documentation
"A function that should be called when items are
enqueued."))
(defsubst phpinspect-make-queue (&optional subscription)
(phpinspect-make-queue-item :subscription subscription))
;; Recursion causes max-eval-depth error here for long queues. Hence the loop
;; implementation for these two functions.
(cl-defmethod phpinspect-queue-last ((item phpinspect-queue-item))
"Get the last item in the queue that ITEM is part of."
(while (phpinspect-queue-item-next item)
(setq item (phpinspect-queue-item-next item)))
item)
(cl-defmethod phpinspect-queue-first ((item phpinspect-queue-item))
"Get the first item in the queue that ITEM is part of."
(while (phpinspect-queue-item-previous item)
(setq item (phpinspect-queue-item-previous item)))
item)
(cl-defmethod phpinspect-queue-enqueue ((item phpinspect-queue-item) thing)
"Add THING to the end of the queue that ITEM is part of."
(let ((last (phpinspect-queue-last item)))
(if (not (phpinspect-queue-item-thing last))
(setf (phpinspect-queue-item-thing last) thing)
(setf (phpinspect-queue-item-next last)
(phpinspect-make-queue-item
:previous last
:thing thing
:subscription (phpinspect-queue-item-subscription item)))))
(when (phpinspect-queue-item-subscription item)
(funcall (phpinspect-queue-item-subscription item))))
(cl-defmethod phpinspect-queue-dequeue ((item phpinspect-queue-item))
"Remove the thing at the front of the queue that ITEM is part of an return it."
(let* ((first (phpinspect-queue-first item))
(thing (phpinspect-queue-item-thing first))
(next (phpinspect-queue-item-next first)))
(when next (setf (phpinspect-queue-item-previous next) nil))
(cond ((and (eq item first) (not next))
(setf (phpinspect-queue-item-thing item)
nil))
((eq item first)
(setf (phpinspect-queue-item-thing item)
(phpinspect-queue-item-thing next))
(setf (phpinspect-queue-item-next item)
(phpinspect-queue-item-next next))))
thing))
(defmacro phpinspect-doqueue (place-and-queue &rest body)
"Loop over queue defined in PLACE-AND-QUEUE executing BODY.
PLACE-AND-QUEUE is a two-member list. The first item should be
the place that the current thing in the queue should be assigned
to upon each iteration. The second item should be a queue-item
belonging to the queue that must be iterated over.
BODY can be any form."
(declare (indent defun))
(let ((item-sym (gensym))
(place (car place-and-queue))
(queue (cadr place-and-queue)))
`(let* ((,item-sym (phpinspect-queue-first ,queue))
(,place (phpinspect-queue-item-thing ,item-sym)))
(when ,place
,@body
(while (setq ,item-sym (phpinspect-queue-item-next ,item-sym))
(setq ,place (phpinspect-queue-item-thing ,item-sym))
,@body)))))
(cl-defmethod phpinspect-queue-find
((item phpinspect-queue-item) thing comparison-func)
"Find THING in the queue that ITEM is part of using COMPARISON-FUNC."
(catch 'found
(phpinspect-doqueue (current-thing item)
(when (funcall comparison-func current-thing thing)
(throw 'found current-thing)))))
(cl-defmethod phpinspect-queue-enqueue-noduplicate
((item phpinspect-queue-item) thing comparison-func)
(when (not (phpinspect-queue-find item thing comparison-func))
(phpinspect-queue-enqueue item thing)))
(cl-defmethod phpinspect-queue-await-insert ((item phpinspect-queue-item))
(condition-wait (phpinspect-queue-item-insert item)))
(cl-defstruct (phpinspect-worker
(:constructor phpinspect-make-worker-generated))
(queue nil
@ -178,10 +75,10 @@ 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))
(cl-defmethod phpinspect-resolve-dynamic-worker ((_worker phpinspect-dynamic-worker))
phpinspect-worker)
(defsubst phpinspect-make-dynamic-worker ()
(defun phpinspect-make-dynamic-worker ()
(phpinspect-make-dynamic-worker-generated))
(defsubst phpinspect-make-worker ()
@ -199,6 +96,7 @@ on the worker independent of dynamic variables during testing.")
(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)))
@ -223,66 +121,59 @@ on the worker independent of dynamic variables during testing.")
"Enqueue a TASK to be executed by WORKER.")
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker) task)
(phpinspect-queue-enqueue (phpinspect-worker-queue worker) task))
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker)
(task phpinspect-index-task))
"Specialized enqueuement method for index tasks. Prevents
indexation tasks from being added when there are identical tasks
already present in the queue."
(phpinspect-queue-enqueue-noduplicate (phpinspect-worker-queue worker) task #'phpinspect-index-task=))
(cl-defmethod phpinspect-index-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
(phpinspect--type= (phpinspect-index-task-type task1) (phpinspect-index-task-type task2))))
(phpinspect--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))
(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 "Worker thead is paused for %d seconds" pause-time)
(run-with-idle-timer
pause-time
nil
(lambda () (with-mutex mx (condition-notify continue))))
(with-mutex mx (condition-wait continue))
(phpinspect--log "Index thread continuing"))
(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.
(ignore-error 'phpinspect-wakeup-thread
(let* ((task (phpinspect-queue-dequeue (phpinspect-worker-queue worker)))
(mx (make-mutex))
(continue (make-condition-variable mx)))
(if task
;; Execute task if it belongs to a project that has not been
;; purged (meaning that it is still actively used).
(unless (phpinspect-project-purged (phpinspect-task-project task))
(phpinspect-task-execute task worker))
;; else: join with the main thread until wakeup is signaled
(thread-join main-thread))
;; Pause for a second after indexing something, to allow user input to
;; interrupt the thread.
(unless (or (not (input-pending-p))
(phpinspect-worker-skip-next-pause worker))
(phpinspect-thread-pause 1 mx continue))
(setf (phpinspect-worker-skip-next-pause worker) nil))))
(condition-case err
(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")
(message "phpinspect worker thread exited")))
(phpinspect-message "phpinspect worker thread exited")))
(cl-defmethod phpinspect-worker-make-thread-function ((worker phpinspect-dynamic-worker))
(phpinspect-worker-make-thread-function
@ -301,7 +192,7 @@ CONTINUE must be a condition-variable"
;; 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)))))))
(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)))
@ -328,45 +219,32 @@ CONTINUE must be a condition-variable"
(interactive)
(phpinspect-worker-stop phpinspect-worker))
(cl-defgeneric phpinspect-make-index-task ((project phpinspect-project)
(type phpinspect--type))
(phpinspect-make-index-task-generated
:project project
:type type))
;;; 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.
(cl-defgeneric phpinspect-task-project (task)
"The project that this task belongs to.")
;; REQUIRED METHODS:
;; - phpinspect-task-execute
;; - phpinspect-task-project
;; OPTIONAL METHODS:
;; - phpinspect-task=
(cl-defmethod phpinspect-task-project ((task phpinspect-index-task))
(phpinspect-index-task-project task))
;;; Code:
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
(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 from index thread"
(phpinspect-index-task-type task)
(phpinspect-project-root project))
(cond (is-native-type
(phpinspect--log "Skipping indexation of native type %s"
(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)))))))
(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

@ -0,0 +1,101 @@
<?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));
}

@ -0,0 +1,35 @@
;;; 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)))))

@ -0,0 +1,20 @@
#!/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 (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil)))))))))
(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) (:terminator ";") (:namespace (:word "App\\Controller") (: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)))))))))

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

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 (: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 ";")))) (: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 (: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 ";"))))))))

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 (: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 ";"))))))))
(: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 ";"))))))))

File diff suppressed because one or more lines are too long

@ -0,0 +1,36 @@
;;; 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; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -26,44 +26,16 @@
(require 'ert)
(require 'phpinspect)
;; 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
(or load-file-name
buffer-file-name))
"Directory that phpinspect tests reside in.")
(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-read-fixture-serialization (name)
(with-temp-buffer
(insert-file-contents-literally (concat phpinspect-test-php-file-directory "/" name ".eld"))
(eval (read (current-buffer)))))
(defun phpinspect-test-parse-fixture-code (name)
(phpinspect-parse-file
(concat phpinspect-test-php-file-directory "/" name ".php")))
(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* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo;"))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo; Whatever comes after don't matter.")
(bmap (phpinspect-parse-string-to-bmap code))
(tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(bmap-context (phpinspect-get-resolvecontext bmap (- (length code) 36)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -78,13 +50,22 @@
context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(phpinspect--make-type-resolver-for-resolvecontext context)))
(bmap-result (phpinspect-get-variable-type-in-block
bmap-context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
result))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
bmap-result)))))
(ert-deftest phpinspect-get-pattern-type-in-block ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;")
(bmap (phpinspect-parse-string-to-bmap "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;"))
(context (phpinspect-get-resolvecontext bmap (length code)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -104,9 +85,25 @@
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
(ert-deftest phpinspect-get-resolvecontext-multi-strategy ()
(let* ((code1 "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]; }}")
(code2 "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]")
(bmap (phpinspect-parse-string-to-bmap code1))
(tokens (phpinspect-parse-string code2))
(context1 (phpinspect-get-resolvecontext bmap (- (length code1) 4)))
(context2 (phpinspect--get-resolvecontext tokens)))
(should (equal (phpinspect--resolvecontext-subject context1)
(phpinspect--resolvecontext-subject context2)))
(should (= (length (phpinspect--resolvecontext-enclosing-tokens context1))
(length (phpinspect--resolvecontext-enclosing-tokens context2))))))
(ert-deftest phpinspect-get-variable-type-in-block-array-access ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]; }}")
(tokens (phpinspect-parse-string-to-bmap code))
(context (phpinspect-get-resolvecontext tokens (- (length code) 4)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -136,8 +133,9 @@
(ert-deftest phpinspect-get-variable-type-in-block-array-foreach ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; foreach ($foo as $bar) {$bar->"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; foreach ($foo as $bar) {$bar->")
(bmap (phpinspect-parse-string-to-bmap code))
(context (phpinspect-get-resolvecontext bmap (length code)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -161,8 +159,9 @@
(ert-deftest phpinspect-get-variable-type-in-block-nested-array ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = [[$baz]]; foreach ($foo[0] as $bar) {$bar->"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = [[$baz]]; foreach ($foo[0] as $bar) {$bar->")
(bmap (phpinspect-parse-string-to-bmap code))
(context (phpinspect-get-resolvecontext bmap (length code)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -216,190 +215,10 @@
(should (equal expected assignments))))
(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-class ()
(let* ((index1
(phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1")))
(index2
(phpinspect-test-read-fixture-serialization "IndexClass1-indexed"))
(index1-class (cdr (alist-get 'classes index1)))
(index2-class (cdr (alist-get 'classes index2))))
(dolist (key '(class-name imports methods static-methods static-variables variables constants extends implements))
(should (equal (alist-get key index1-class)
(alist-get key index2-class))))))
(ert-deftest phpinspect-get-resolvecontext ()
(let ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data "IncompleteClass"))))
(should (equal (phpinspect--resolvecontext-subject resolvecontext)
'((:variable "this")
(:object-attrib (:word "em"))
(:object-attrib nil))))
(should (phpinspect-root-p
(car (last (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))))
(should (phpinspect-incomplete-list-p
(car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(should (phpinspect-incomplete-function-p
(cadr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(should (phpinspect-incomplete-class-p
(cadddr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext ()
(let* ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data "IncompleteClass")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver
(phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver
(phpinspect--make-type :name "GastonLagaffe"))))
(should (phpinspect--type=
(phpinspect--make-type :name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver
(phpinspect--make-type :name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext-namespace-block ()
(let* ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data
"IncompleteClassBlockedNamespace")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver (phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver (phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver (phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type
:name "GastonLagaffe"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type :name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-type-resolver-for-resolvecontext-multiple-namespace-blocks ()
(let* ((resolvecontext (phpinspect--get-resolvecontext
(phpinspect-test-read-fixture-data
"IncompleteClassMultipleNamespaces")))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext)))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "array"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\array")
(funcall type-resolver
(phpinspect--make-type :name "\\array"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\Symfony\\Component\\HttpFoundation\\Response")
(funcall type-resolver (phpinspect--make-type :name "Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Response")
(funcall type-resolver
(phpinspect--make-type :name "\\Response"))))
(should (phpinspect--type= (phpinspect--make-type :name "\\App\\Controller\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type :name "GastonLagaffe"))))
(should (phpinspect--type= (phpinspect--make-type
:name "\\App\\Controller\\Dupuis\\GastonLagaffe")
(funcall type-resolver (phpinspect--make-type
:name "Dupuis\\GastonLagaffe"))))))
(ert-deftest phpinspect-resolve-type-from-context ()
(let* ((token-tree (phpinspect-parse-string "
(let* ((pctx (phpinspect-make-pctx :incremental t :bmap (phpinspect-make-bmap)))
(code "
namespace Amazing;
class FluffBall
@ -416,8 +235,18 @@ class FluffBall
$ball = $this->fluffer;
if ($ball) {
if(isset($ball->fluff()->poof->upFluff->"))
(fluffer (phpinspect-parse-string "
if(isset($ball->fluff()->poof->upFluff->)) {
$this->beFluffy();
}
}
$ball->fluff()->poof->
}
}")
(token-tree (phpinspect-with-parse-context pctx
(phpinspect-parse-string code)))
(bmap (phpinspect-pctx-bmap pctx))
(fluffer (phpinspect-parse-string "
namespace Amazing;
use Vendor\\FluffLib\\Fluff;
@ -448,7 +277,7 @@ class FlufferUpper
}
}"))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(context (phpinspect--get-resolvecontext token-tree)))
(context (phpinspect-get-resolvecontext bmap 310)))
(setf (phpinspect--resolvecontext-project-root context)
"phpinspect-test")
@ -464,63 +293,20 @@ class FlufferUpper
(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)
(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)
(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-current-project-root)
(cdar (alist-get 'classes (cdr index))))
context))))
(should (string= "doThing: ($moment DateTime, $thing Thing, $other): Thing"
(with-temp-buffer
(insert php-code)
(phpinspect-eldoc-function))))))
(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))))))
(ert-deftest phpinspect-resolve-type-from-context-static-method ()
(let* ((php-code "
(with-temp-buffer
(insert "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
@ -531,11 +317,14 @@ class Thing
function doStuff()
{
self::doThing()->")
(tokens (phpinspect-parse-string php-code))
(let* ((bmap (phpinspect-make-bmap))
(tokens (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap bmap)
(phpinspect-parse-current-buffer)))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(context (phpinspect--get-resolvecontext tokens)))
(context (phpinspect-get-resolvecontext bmap (point))))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
@ -545,10 +334,11 @@ class Thing
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))))
context)))))))
(ert-deftest phpinspect-resolve-type-from-context-static-method-with-preceding-words ()
(let* ((php-code "
(with-temp-buffer
(insert "
class Thing
{
static function doThing(\\DateTime $moment, Thing $thing, $other): static
@ -560,21 +350,24 @@ class Thing
{
if (true) {
return self::doThing()->")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(context (phpinspect--get-resolvecontext tokens)))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(let* ((bmap (phpinspect-make-bmap))
(tokens (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t
:bmap bmap)
(phpinspect-parse-current-buffer)))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
(phpinspect-eldoc-word-width 100)
(context (phpinspect-get-resolvecontext bmap (point))))
(phpinspect-purge-cache)
(phpinspect-cache-project-class
(phpinspect-current-project-root)
(cdar (alist-get 'classes (cdr index))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context))))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
(phpinspect-resolve-type-from-context
context
(phpinspect--make-type-resolver-for-resolvecontext
context)))))))
(ert-deftest phpinspect-get-last-statement-in-token-with-static-attribute-context ()
(let* ((php-code-function "
@ -634,9 +427,20 @@ class Thing
(should (equal '((:variable "wat") (:object-attrib "call"))
(phpinspect--assignment-from (car result))))))
(ert-deftest phpinspect-parse-function-missing-open-block ()
(let ((parsed (phpinspect-parse-string "function bla() echo 'Hello'}")))
(should (equal '(:root (:function
(:declaration (:word "function") (:word "bla") (:list)
(:word "echo") (:word "Hello"))))
parsed))))
(ert-deftest phpinspect-parse-string-token ()
(let ((parsed (phpinspect-parse-string "<?php 'string'")))
(should (equal '(:root (:string "string")) parsed))))
(load-file (concat phpinspect-test-directory "/test-worker.el"))
(load-file (concat phpinspect-test-directory "/test-autoload.el"))
(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"))
@ -644,6 +448,16 @@ class Thing
(load-file (concat phpinspect-test-directory "/test-class.el"))
(load-file (concat phpinspect-test-directory "/test-type.el"))
(load-file (concat phpinspect-test-directory "/test-util.el"))
(load-file (concat phpinspect-test-directory "/test-bmap.el"))
(load-file (concat phpinspect-test-directory "/test-edtrack.el"))
(load-file (concat phpinspect-test-directory "/test-resolvecontext.el"))
(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,6 +1,6 @@
;; test-autoload.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
; test-autoload.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -27,92 +27,99 @@
(require 'ert)
(require 'phpinspect-fs)
(require 'phpinspect-autoload)
(require 'phpinspect-resolvecontext)
(ert-deftest phpinspect-psr0-fill-typehash ()
(let* ((fs (phpinspect-make-virtual-fs))
(typehash (make-hash-table :size 10 :test 'eq))
(autoload
(phpinspect-make-psr0-generated :prefix "App\\")))
(phpinspect-virtual-fs-set-file
fs "/home/user/projects/app/src/App/Services/SuperService.php" "")
(ert-deftest phpinspect-filename-to-typename ()
(should (eq (phpinspect-intern-name "\\Foo\\Bar") (phpinspect-filename-to-typename "src/" "src/Foo////////Bar.php")))
(phpinspect-virtual-fs-set-file
fs "/home/user/projects/app/src/Kernel.php" "")
(should (eq (phpinspect-intern-name "\\Foo\\Bar") (phpinspect-filename-to-typename "src/somewhere/else/" "src/somewhere/else/Foo/Bar.php"))))
(phpinspect-virtual-fs-set-file
fs "/home/user/projects/app/src/App/Controller/Banana.php" "")
(phpinspect-virtual-fs-set-file
fs "/home/user/projects/app/lib/Mailer_Lib.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/\"}}}")
(setf (phpinspect-psr0-directories autoload) (list "/home/user/projects/app/src/"
"/home/user/projects/app/lib/"))
(phpinspect-virtual-fs-set-file fs
"/root/vendor/runescape/client/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(phpinspect-al-strategy-fill-typehash autoload fs typehash)
(should-not (hash-table-empty-p typehash))
(phpinspect-virtual-fs-set-file fs
"/root/vendor/apples/pears/composer.json"
"{\"autoload\": { \"psr-0\": {\"Runescape\\\\Banana\\\\\": [\"src/\", \"lib\"]}}}")
(should (string= "/home/user/projects/app/src/App/Services/SuperService.php"
(gethash (phpinspect-intern-name "\\App\\Services\\SuperService")
typehash)))
(should (string= "/home/user/projects/app/src/Kernel.php"
(gethash (phpinspect-intern-name "\\Kernel")
typehash)))
(should (string= "/home/user/projects/app/src/App/Controller/Banana.php"
(gethash (phpinspect-intern-name "\\App\\Controller\\Banana")
typehash)))
(let ((sorter (lambda (file1 file2) (string-lessp (cdr file1) (cdr file2)))))
(should (string= "/home/user/projects/app/lib/Mailer_Lib.php"
(gethash (phpinspect-intern-name "\\Mailer_Lib")
typehash)))))
(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-psr4-fill-typehash ()
(ert-deftest phpinspect-autoload-composer-json-iterator ()
(let* ((fs (phpinspect-make-virtual-fs))
(typehash (make-hash-table :size 10 :test 'eq))
(autoload
(phpinspect-make-psr4-generated :prefix "App\\")))
(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 "/home/user/projects/app/src/Services/SuperService.php" "")
(phpinspect-virtual-fs-set-file
fs "/home/user/projects/app/src/Kernel.php" "")
(phpinspect-virtual-fs-set-file fs
"/root/composer.json"
"{ \"autoload\": { \"psr-4\": {\"WoW\\\\Dwarves\\\\\": \"src/\"}}}")
(phpinspect-virtual-fs-set-file
fs "/home/user/projects/app/src/Controller/Banana.php" "")
(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 "/home/user/projects/app/lib/Mailer_Lib.php" "")
(setf (phpinspect-psr4-directories autoload) (list "/home/user/projects/app/src/"
"/home/user/projects/app/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))
(phpinspect-al-strategy-fill-typehash autoload fs typehash)
(should-not error)
(should-not (hash-table-empty-p typehash))
(should (= 4 (length result)))
(should (= 2 (length (seq-filter #'phpinspect-psr0-p result))))
(should (= 2 (length (seq-filter #'phpinspect-psr4-p result))))))
(should (string= "/home/user/projects/app/src/Services/SuperService.php"
(gethash (phpinspect-intern-name "\\App\\Services\\SuperService")
typehash)))
(should (string= "/home/user/projects/app/src/Kernel.php"
(gethash (phpinspect-intern-name "\\App\\Kernel")
typehash)))
(should (string= "/home/user/projects/app/src/Controller/Banana.php"
(gethash (phpinspect-intern-name "\\App\\Controller\\Banana")
typehash)))
(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 (string= "/home/user/projects/app/lib/Mailer_Lib.php"
(gethash (phpinspect-intern-name "\\App\\Mailer_Lib")
typehash)))))
(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-autoloader-refresh ()
(ert-deftest phpinspect-al-strategy-execute ()
(let* ((fs (phpinspect-make-virtual-fs))
(project (phpinspect--make-project
:fs fs
:root "/project/root"))
(project (phpinspect--make-project :root "/project/root" :fs fs))
(autoloader (phpinspect-make-autoloader
:project project)))
: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"
@ -139,17 +146,38 @@
(phpinspect-virtual-fs-set-file
fs
"/project/root/vendor/not-runescape/wow/composer.json"
"{ \"autoload\": { \"psr-4\": {\"WoW\\\\Dwarves\\\\\": \"src/\"}}}")
"{ \"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" "")
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))
(phpinspect-autoloader-refresh autoloader)
(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"))))))
(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"))))))

@ -0,0 +1,91 @@
;; -*- 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,6 +1,6 @@
;;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -26,43 +26,468 @@
(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-parse-buffer-location-map ()
"Confirm that the location map of `phpinspect-current-buffer' is
populated when the variable is set and the data in it is accurate."
(let* ((location-map)
(parsed)
(class))
(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))
(setq location-map
(phpinspect-buffer-location-map phpinspect-current-buffer)))
(let* ((class (seq-find #'phpinspect-class-p
(seq-find #'phpinspect-namespace-p parsed)))
(class-region (gethash class location-map))
(classname-region (gethash (car (cddadr class)) location-map)))
(should class)
(should class-region)
(should classname-region)
;; Character position of the start of the class token.
(should (= 611 (phpinspect-region-start class-region)))
(should (= 2367 (phpinspect-region-end class-region)))
(should (= 617 (phpinspect-region-start classname-region)))
(should (= 634 (phpinspect-region-end classname-region))))))
(let* ((class (seq-find #'phpinspect-class-p
(seq-find #'phpinspect-namespace-p parsed)))
(classname (car (cddadr class))))
(let ((tokens (phpinspect-buffer-tokens-enclosing-point
phpinspect-current-buffer 617)))
(should (eq classname
(phpinspect-meta-token (car tokens))))
(should (phpinspect-declaration-p (phpinspect-meta-token (cadr tokens))))
(should (eq class (phpinspect-meta-token (caddr tokens)))))))))
(ert-deftest phpinspect-parse-buffer-no-current ()
"Confirm that the parser is still functional with
`phpinspect-current-buffer' unset."
(let*((buffer)
(parsed))
(with-temp-buffer
(should-not phpinspect-current-buffer)
(insert-file-contents (concat phpinspect-test-php-file-directory "/NamespacedClass.php"))
(setq parsed (phpinspect-parse-current-buffer)))
(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,6 +1,6 @@
;; test-class.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -25,6 +25,11 @@
(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"))
@ -40,3 +45,69 @@
(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)))))

@ -0,0 +1,134 @@
;;; 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)))))

@ -0,0 +1,113 @@
;;; 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,6 +1,6 @@
;;; test-autoload.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>

@ -1,6 +1,6 @@
;;; test-index.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -24,6 +24,13 @@
;;; 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
@ -44,15 +51,24 @@
`(phpinspect--root-index
(imports)
(classes
(,(phpinspect--make-type :name"\\Potato" :fully-qualified t)
(,(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
@ -83,10 +99,11 @@ return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->anti
(should (equal
(mapcar #'phpinspect-intern-name
(sort
'("Cheese" "Bacon" "Ham" "Bagel" "Monkey" "ExtendedThing"
"StaticThing" "Thing" "ThingFactory" "Potato" "OtherThing")
(copy-sequence
'("Cheese" "Bacon" "Ham" "Bagel" "Monkey" "ExtendedThing"
"StaticThing" "Thing" "ThingFactory" "Potato" "OtherThing"))
#'string<))
(sort used-types (lambda (s1 s2) (string< (symbol-name s1) (symbol-name s2))))))))
(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 `(
@ -102,7 +119,7 @@ return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->anti
("Request" "Response")))))
(dolist (set blocks)
(let ((result (phpinspect--find-used-types-in-tokens (car set))))
(should (equal (cadr set) result))))))
(should (equal (sort (copy-sequence (cadr set)) #'string-lessp) (sort result #'string-lessp)))))))
(ert-deftest phpinspect-index-method-annotations ()
(let* ((result (phpinspect--index-tokens
@ -145,3 +162,89 @@ return StaticThing::create(new ThingFactory())->makeThing((((new Potato())->anti
(should (phpinspect--type=
(phpinspect--make-type :name "\\void" :fully-qualified t)
(phpinspect--function-return-type method))))))))
(ert-deftest phpinspect-index-tokens-class ()
(let* ((index1
(phpinspect--index-tokens
(phpinspect-test-read-fixture-data "IndexClass1")))
(index2
(phpinspect-test-read-fixture-serialization "IndexClass1-indexed"))
(index1-class (car (alist-get 'classes index1)))
(index2-class (car (alist-get 'classes index2))))
(dolist (key '(class-name imports methods static-methods static-variables variables constants extends implements))
(should (equal (alist-get key index1-class)
(alist-get key index2-class))))))
(ert-deftest phpinspect-index-bmap-class ()
(let* ((pctx (phpinspect-make-pctx :incremental t :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))))

@ -0,0 +1,42 @@
;; 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))))

@ -0,0 +1,53 @@
;;; 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)))))

@ -0,0 +1,146 @@
;; 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))))

@ -0,0 +1,66 @@
;;; 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,6 +1,6 @@
;; test-project.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -35,11 +35,9 @@
(ert-deftest phpinspect-project-watch-file-and-purge ()
(let* ((root (make-temp-file "phpinspect-test" 'dir))
(fs (phpinspect-make-fs))
(worker (phpinspect-make-worker))
(watch-file (concat root "/watch1"))
(project (phpinspect--make-project :fs fs :root root)))
(phpinspect-project-watch-file project watch-file
(lambda (&rest ignored)))
(phpinspect-project-watch-file project watch-file #'ignore)
(phpinspect-project-purge project)

@ -0,0 +1,112 @@
;; -*- 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"))))))

@ -0,0 +1,186 @@
;; 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)))))

@ -0,0 +1,57 @@
;;; 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,6 +1,6 @@
;; test-type.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -23,6 +23,8 @@
;;; Code:
(require 'phpinspect-type)
(ert-deftest phpinspect--resolve-late-static-binding ()
(let* ((sets '(("\\bool" . "\\bool")
("\\static" . "\\AType")

@ -1,6 +1,6 @@
;; test-util.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;;; test-util.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -23,25 +23,35 @@
;;; 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")))
(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"))
(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,6 +1,6 @@
;;; test-worker.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
@ -35,7 +35,8 @@
(should (string= "one" (phpinspect-queue-dequeue queue)))
(should (string= "two" (phpinspect-queue-dequeue queue)))
(should (string= "three" (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))
@ -70,7 +71,7 @@
;; attempt to iterate over an empty queue
(let ((have-iterated nil))
(phpinspect-doqueue (thing (phpinspect-make-queue))
(phpinspect-doqueue (_thing (phpinspect-make-queue))
(setq have-iterated t))
(should-not have-iterated)))

@ -3,12 +3,10 @@
(require 'phpinspect-index)
(require 'phpinspect-serialize)
(let ((here (file-name-directory
(or load-file-name
buffer-file-name)))
(let ((here (file-name-directory (macroexp-file-name)))
(print-length 1000)
(print-level 1000))
(dolist (file (directory-files (concat here "/../fixtures" ) t "\\.php$"))
(dolist (file (directory-files (expand-file-name "../fixtures" here) t "\\.php\\'"))
(with-temp-buffer
(insert-file-contents-literally file)
(let ((result (phpinspect-parse-current-buffer)))

Loading…
Cancel
Save