[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/phpinspect dbf0ec0390 051/126: Transition from index sc
From: |
ELPA Syncer |
Subject: |
[elpa] externals/phpinspect dbf0ec0390 051/126: Transition from index script to autoloader |
Date: |
Sat, 12 Aug 2023 00:58:41 -0400 (EDT) |
branch: externals/phpinspect
commit dbf0ec03903e4ec795659c982e772d147ea1144c
Author: Hugo Thunnissen <devel@hugot.nl>
Commit: Hugo Thunnissen <devel@hugot.nl>
Transition from index script to autoloader
---
README.md | 2 +-
phpinspect-autoload.el | 56 +++-
phpinspect-buffer.el | 81 +++++
phpinspect-cache.el | 25 +-
phpinspect-class.el | 7 +-
phpinspect-imports.el | 155 +++++++++
phpinspect-index.bash | 853 ------------------------------------------------
phpinspect-index.el | 131 ++++++--
phpinspect-parser.el | 17 +-
phpinspect-project.el | 58 +++-
phpinspect-type.el | 2 +-
phpinspect-worker.el | 73 +++--
phpinspect.el | 172 ++++------
test/phpinspect-test.el | 6 +-
test/test-buffer.el | 68 ++++
test/test-index.el | 103 ++++++
test/test-project.el | 46 +++
17 files changed, 782 insertions(+), 1073 deletions(-)
diff --git a/README.md b/README.md
index cb50b05541..b5013d5a5b 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ WIP. More documentation is in the making.
(setq-local company-backends '(phpinspect-company-backend))
;; Shortcut to add use statements for classes you use.
- (define-key php-mode-map (kbd "C-c u") 'phpinspect-fix-uses-interactive)
+ (define-key php-mode-map (kbd "C-c u") 'phpinspect-fix-imports)
;; Shortcuts to quickly search/open files of PHP classes.
(global-set-key (kbd "C-c a") 'phpinspect-find-class-file)
diff --git a/phpinspect-autoload.el b/phpinspect-autoload.el
index 51efa36f95..fb921f3713 100644
--- a/phpinspect-autoload.el
+++ b/phpinspect-autoload.el
@@ -66,7 +66,13 @@
(types (make-hash-table :test 'eq :size 10000 :rehash-size 10000)
:type hash-table
:documentation
- "The external types that can be autoloaded through this autoloader."))
+ "The external types that can be autoloaded through this autoloader.")
+ (type-name-fqn-bags (make-hash-table :test 'eq :size 3000 :rehash-size 3000)
+ :type hash-table
+ :documentation
+ "Hash table that contains lists of fully
+qualified names congruent with a bareword type name. Keyed by
+bareword typenames."))
(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."
@@ -108,13 +114,22 @@
(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 (phpinspect--read-json-file
- fs
- (concat project-root "/composer.json")))
- (project-autoload (gethash "autoload" composer-json))
+ (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)))
+ (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)
@@ -137,8 +152,18 @@
dependency-dir fs types)
dependency-autoload))))))))
- (setf (phpinspect-autoloader-own-types autoloader) own-types)
- (setf (phpinspect-autoloader-types autoloader) types)))
+ (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)))
(cl-defmethod phpinspect-autoloader-resolve ((autoloader phpinspect-autoloader)
typename-symbol)
@@ -152,13 +177,16 @@
(defsubst phpinspect-filename-to-typename (dir filename &optional prefix)
(phpinspect-intern-name
- (concat "\\"
- (or prefix "")
- (replace-regexp-in-string
- "/" "\\\\"
- (string-remove-suffix
- ".php"
- (string-remove-prefix dir filename))))))
+ (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
diff --git a/phpinspect-buffer.el b/phpinspect-buffer.el
new file mode 100644
index 0000000000..21e9d4e422
--- /dev/null
+++ b/phpinspect-buffer.el
@@ -0,0 +1,81 @@
+;;; phpinspect-buffer.el --- PHP parsing and completion package -*-
lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc
+
+;; Author: Hugo Thunnissen <devel@hugot.nl>
+;; Keywords: php, languages, tools, convenience
+;; Version: 0
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(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)
+
+(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.")
+ (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."))
+
+(cl-defmethod phpinspect-buffer-parse ((buffer phpinspect-buffer))
+ "Parse the PHP code in the the emacs buffer that this object is
+linked with."
+ (with-current-buffer (phpinspect-buffer-buffer buffer)
+ (setf (phpinspect-buffer-location-map buffer)
+ (make-hash-table :test 'eq
+ :size 400
+ :rehash-size 400))
+
+ (let ((tree (phpinspect-parse-current-buffer)))
+ (setf (phpinspect-buffer-tree buffer) tree)
+ tree)))
+
+(cl-defmethod phpinspect-buffer-token-location ((buffer phpinspect-buffer)
token)
+ (gethash token (phpinspect-buffer-location-map buffer)))
+
+(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))
+ tokens))
+
+(provide 'phpinspect-buffer)
diff --git a/phpinspect-cache.el b/phpinspect-cache.el
index c23535866c..a401b50d41 100644
--- a/phpinspect-cache.el
+++ b/phpinspect-cache.el
@@ -24,14 +24,9 @@
;;; Code:
(require 'phpinspect-project)
+(require 'phpinspect-autoload)
(cl-defstruct (phpinspect--cache (:constructor phpinspect--make-cache))
- (active-projects nil
- :type alist
- :documentation
- "An `alist` that contains the root directory
- paths of all currently active phpinspect
- projects")
(projects (make-hash-table :test 'equal :size 10)
:type hash-table
:documentation
@@ -54,12 +49,18 @@ then returned.")
(cl-defmethod phpinspect--cache-get-project-create
((cache phpinspect--cache) (project-root string))
- (or (phpinspect--cache-getproject cache project-root)
- (puthash project-root
- (phpinspect--make-project-cache
- :root project-root
- :worker (phpinspect-make-dynamic-worker))
- (phpinspect--cache-projects cache))))
+ (let ((project (phpinspect--cache-getproject 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)))
+ project))
(provide 'phpinspect-cache)
;;; phpinspect.el ends here
diff --git a/phpinspect-class.el b/phpinspect-class.el
index c524ad128b..199b1fb015 100644
--- a/phpinspect-class.el
+++ b/phpinspect-class.el
@@ -61,12 +61,7 @@
:type bool
:documentation
"A boolean indicating whether or not this class
- has been indexed yet.")
- (index-queued nil
- :type bool
- :documentation
- "A boolean indicating whether the class type has
- been queued for indexation"))
+ has been indexed yet."))
(cl-defmethod phpinspect--class-trigger-update ((class phpinspect--class))
(dolist (sub (phpinspect--class-subscriptions class))
diff --git a/phpinspect-imports.el b/phpinspect-imports.el
new file mode 100644
index 0000000000..c6c7d0957d
--- /dev/null
+++ b/phpinspect-imports.el
@@ -0,0 +1,155 @@
+; phpinspect-imports.el --- PHP parsing and completion package -*-
lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc
+
+;; Author: Hugo Thunnissen <devel@hugot.nl>
+;; Keywords: php, languages, tools, convenience
+;; Version: 0
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; See docstrings for documentation, starting with `phpinspect-mode'.
+
+;;; Code:
+
+(require 'phpinspect-parser)
+(require 'phpinspect-index)
+(require 'phpinspect-autoload)
+(require 'phpinspect-buffer)
+
+(defun phpinspect-insert-at-point (point data)
+ (save-excursion
+ (goto-char point)
+ (insert data)))
+
+(defun phpinspect-add-use (fqn buffer &optional namespace-token)
+ "Add use statement for FQN to BUFFER.
+
+If NAMESPACE-TOKEN is non-nil, it is assumed to be a token that
+was parsed from BUFFER and its location will be used to find a
+buffer position to insert the use statement at."
+ (when (string-match "^\\\\" fqn)
+ (setq fqn (string-trim-left fqn "\\\\")))
+
+ (if namespace-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 existing-use
+ (phpinspect-insert-at-point
+ (phpinspect-region-start
+ (phpinspect-buffer-token-location buffer existing-use))
+ (format "use %s;%c" fqn ?\n))
+ (if namespace-block
+ (phpinspect-insert-at-point
+ (+ 1 (phpinspect-region-start
+ (phpinspect-buffer-token-location buffer
namespace-block)))
+ (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)))
+ (format "%c%cuse %s;%c" ?\n ?\n fqn ?\n)))))
+ ;; else
+ (let ((existing-use (seq-find #'phpinspect-use-p
+ (phpinspect-buffer-tree buffer))))
+ (if existing-use
+ (phpinspect-insert-at-point
+ (phpinspect-region-start
+ (phpinspect-buffer-token-location buffer existing-use))
+ (format "use %s;%c" fqn ?\n))
+ (let ((first-token (cadr (phpinspect-buffer-tree buffer))))
+ (if (and (phpinspect-word-p first-token)
+ (string= "declare" (cadr first-token)))
+ (phpinspect-insert-at-point
+ (phpinspect-region-end
+ (phpinspect-buffer-token-location
+ buffer (seq-find #'phpinspect-terminator-p
(phpinspect-buffer-tree buffer))))
+ (format "%c%cuse %s;%c" ?\n ?\n fqn ?\n))
+ (phpinspect-insert-at-point
+ (phpinspect-region-start
+ (phpinspect-buffer-token-location buffer first-token))
+ (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 (symbol-name (completing-read "Class: " fqns))
+ buffer namespace-token))
+ (t (message "No import found for type %s" typename))))))
+
+(defun phpinspect-namespace-part-of-typename (typename)
+ (string-trim-right typename "\\\\?[^\\\\]+"))
+
+(defalias 'phpinspect-fix-uses-interactive #'phpinspect-fix-imports
+ "Alias for backwards compatibility")
+
+(defun phpinspect-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))
+ (index (phpinspect--index-tokens
+ tree nil (lambda (token) (gethash token location-map))))
+ (classes (alist-get 'classes index))
+ (imports (alist-get 'imports index))
+ (project (phpinspect--cache-get-project-create
+ (phpinspect--get-or-create-global-cache)
+ (phpinspect-project-root))))
+ (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)))))))))
+
+(provide 'phpinspect-imports)
diff --git a/phpinspect-index.bash b/phpinspect-index.bash
deleted file mode 100755
index 357444f611..0000000000
--- a/phpinspect-index.bash
+++ /dev/null
@@ -1,853 +0,0 @@
-#!/bin/bash
-##
-# phpinspect-index.bash - Resolve namespaces and fix missing use statements in
your PHP
-# scripts.
-###
-# This script is derived from phpns, an earlier project that had a much wider
scope than
-# just index files for phpinspect.el. Much of the code and command line
argument options
-# can be removed.
-# TODO: remove whatever functionality is not required for phpinspect.el
-
-# shellcheck disable=SC2155
-declare CACHE_DIR=./.cache/phpinspect
-declare INFO=1
-
-# Cache locations
-declare CLASSES="$CACHE_DIR/classes"
-declare NAMESPACES="$CACHE_DIR/namespaces"
-declare USES="$CACHE_DIR/uses"
-declare USES_OWN="$CACHE_DIR/uses_own"
-declare USES_LOOKUP="$CACHE_DIR/uses_lookup"
-declare USES_LOOKUP_OWN="$CACHE_DIR/uses_lookup_own"
-declare FILE_PATHS="$CACHE_DIR/file_paths"
-declare NAMESPACE_FILE_PATHS="$CACHE_DIR/namespace_file_paths"
-declare INDEXED="$CACHE_DIR/indexed"
-
-[[ $DEBUG -eq 2 ]] && set -x
-shopt -s extglob
-shopt -so pipefail
-
-read -rd '' USAGE <<'EOF'
- phpns - Resolve namespaces and fix missing use statements in your PHP
scripts.
-
- USAGE:
- phpns COMMAND [ ARGUMENTS ] [ OPTIONS ]
-
- COMMANDS:
- i, index Index the PHP project in the
current directory
- fu, find-use CLASS_NAME Echo the FQN of a class
- fxu, fix-uses FILE Add needed use statements to
FILE
- cns, classes-in-namespace NAMESPACE Echo the classes that reside in
NAMESPACE
- fp, filepath FQN Echo the filepath of the class
by the name of FQN.
-
- TO BE IMPLEMENTED:
- rmuu, remove-unneeded-uses FILE: Remove all use statements for classes
that are not being used.
-
- OPTIONS FOR ALL COMMANDS:
- -s --silent Don't print info.
-
- UNIQUE OPTIONS PER COMMAND:
- index:
- -d, --diff Show differences between the files in
the index and the files in the project directory.
- -N, --new Only index new files
- find-use:
- -j, --json Provide possible use FQN's as a json
array.
- -p, --prefer-own If there are matches inside the "src"
dir, only use those.
- -a, --auto-pick Use first encountered match, don't
provide a choice.
- -b. --bare Print FQN's without any additives.
- fix-uses:
- -j, --json Provide possible use FQN's per class as
a json object with the class names as keys.
- -p, --prefer-own If there are matches inside the "src"
dir, only use those.
- -a, --auto-pick Use first encountered match, for every
class, don't provide a choice.
- -o, --stdout Print to stdout in stead of printing to
the selected file.
- filepath: -
-
-EOF
-
-execute() {
- declare command="$1" INFO="$INFO"
- declare -a CONFIG=()
- shift
-
- if [[ $command == @(-h|--help|help) ]]; then
- echo "$USAGE" >&2
- exit 0
- fi
-
- if ! [[ -f ./composer.json ]] && ! [[ -d ./.git ]]; then
- echo "No composer.json or .git file found, not in root of poject,
exiting." >&2
- exit 1
- fi
-
- case "$command" in
- i | index)
- handleArguments index "$@" || return $?
-
- # The arguments to grep need to be dynamic here, because the diff
option
- # requires different arguments to be passed to grep.
- declare -a grep_args=(
- -H
-
'^\(class\|abstract[[:blank:]]\+class\|\(final[[:blank:]]\+\|/\*[[:blank:]]*final[[:blank:]]*\*/[[:blank:]]*\)class\|namespace\|interface\|trait\)[[:blank:]]\+[A-Za-z]\+'
- --exclude-dir={.cache,var,bin}
- --binary-files=without-match
- )
-
- # Only index new files
- if [[ ${CONFIG[$INDEX_NEW]} == '--new' ]]; then
- declare -a new_files=() deleted_files=()
-
- # Extract new files from diff.
- while IFS=':' read -ra diff_file; do
- if [[ ${diff_file[0]} == '-' ]]; then
- deleted_files=("${diff_file[1]}" "${deleted_files[@]}")
- elif [[ ${diff_file[0]} == '+' ]]; then
- new_files=("${diff_file[1]}" "${new_files[@]}")
- fi
- done < <(diffIndex)
-
- # Inform the user if non-existent files were found. Right now
the only
- # way to fix this is to reindex entirely.
- if [[ ${#deleted_files[@]} -gt 0 ]]; then
- info "There are ${#deleted_files[@]} non-existent files in
your index. Consider reindexing to prevent incorrect results."
- info 'Some of these none existent files are:'
- for i in {0..19}; do
- [[ $i -ge ${#deleted_files[@]} ]] && break
- infof ' - "%s"\n' "${deleted_files[$i]}"
- done
- fi
-
- if [[ ${#new_files[@]} -eq 0 ]]; then
- info 'No new files were found.'
- return 0
- else
- info "${#new_files[@]} new files found to index."
- fi
-
- # To exclusively index new files, add the filenames to the
arguments array
- grep_args=("${grep_args[@]}" "${new_files[@]}")
- elif [[ ${CONFIG[$INDEX_DIFF]} == '--diff' ]]; then
- diffIndex
- return $?
- else
- grep_args=("${grep_args[@]}" '-r' '--include=*.php')
- fi
-
- # Index matching files
- grep -m 2 "${grep_args[@]}" | grep -v '^vendor/bin' | fillIndex
-
- # Add non-matching files to the file with indexed files.
- # This is necessary to be able to diff the index.
- grep -L "${grep_args[@]}" | grep -v '^vendor/bin' >> "$INDEXED"
- ;;
- fu | find-use)
- checkCache
- handleArguments find-use "$@" || return $?
- declare use_path='' class_name="${CONFIG[$CLASS_NAME]}"
- if [[ "$class_name" == @(array|string|float|int|void|mixed) ]];
then
- infof 'Type "%s" is not a class, but a primitive type.\n'
"$class_name"
- return 1
- fi
-
- findUsePathForClass "$class_name"
- ;;
- fxu | fix-uses)
- checkCache
- handleArguments fix-uses "$@" || return $?
-
- declare file="${CONFIG[$FILE]}"
-
- if ! [[ -f $file ]]; then
- infof 'File "%s" does not exist or is not a regular file.\n'
"$file"
-
- return 1
- elif [[ ${CONFIG[$STDOUT]} == '--stdout' ]]; then
- fixMissingUseStatements "$file"
- else
- # shellcheck disable=SC2005
- echo "$(fixMissingUseStatements "$file")" > "$file"
- fi
- ;;
- ns | namespace)
- checkCache
- declare file="$1"
-
- # Try the index, if that doesn't work, attempt to extract the
namespace from the file itself.
- if ! grep "(?<=$file:).*" "$NAMESPACE_FILE_PATHS"; then
- grep -Po '(?<=^namespace[[:blank:]])[A-Za-z_\\]+' "$file"
- fi
- ;;
- cns | classes-in-namespace)
- handleArguments classes-in-namespace "$@" || return $?
- checkCache
-
- declare namespace="${CONFIG[$NAMESPACE]}\\"
- debug "Checking for namespace $namespace"
-
- awk -F ':' "/:${namespace//\\/\\\\}"'[^\\]+$/{ print $1; }'
"$USES_LOOKUP"
- ;;
- fp | filepath)
- handleArguments filepath "$@" || return $?
- checkCache
-
- grep -Po "^.*(?=:${CONFIG[$CLASS_PATH]//\\/\\\\}$)" "$FILE_PATHS"
- ;;
- *)
- printf 'Command "%s" is not a valid subcommand.\n' "$command" >&2
- exit 1
- ;;
- esac
-}
-
-# shellcheck disable=SC2034
-fixMissingUseStatements() {
- declare check_uses='false' check_needs='false' file="$1" namespace="$2"
- declare -A uses=() needs=() namespace=()
- declare -a classes=()
-
- classes=($(execute cns "$(execute ns "$file")"))
- for class in "${classes[@]}"; do
- namespace["$class"]='in_namespace'
- done
-
- findUsesAndNeeds < "$file"
- addUseStatements "${!needs[@]}" < "$file"
-}
-
-findUsePathForClass() {
- declare class="$1"
- if [[ ${CONFIG[$PREFER_OWN]} == '--prefer-own' ]]; then
- declare -a possibilities=($(grep -Po "(?<=^${CONFIG[$CLASS_NAME]}:).*"
"$USES_LOOKUP_OWN"))
- else
- declare -a possibilities=($(grep -Po "(?<=^${CONFIG[$CLASS_NAME]}:).*"
"$USES_LOOKUP"))
- fi
-
- if [[ ${#possibilities[@]} -eq 1 ]]; then
- use_path="${possibilities[0]}"
- debugf 'Single use path "%s" found' "${possibilities[0]}"
-
- # Provide an escaped string for json output if requested.
- [[ ${CONFIG[$JSON]} == '--json' ]] && printf -v use_path '"%s"'
"${use_path//\\/\\\\}"
- elif [[ ${#possibilities[@]} -eq 0 ]]; then
- _handle_no_use
- return $?
- else
- _handle_multiple_uses
- fi
-
- infof 'Found use statement for "%s"\n' "$use_path" >&2
- if [[ ${CONFIG[$JSON]} == '--json' ]]; then
- echo '['
- echo "$use_path"
- printf ']'
- elif [[ ${CONFIG[$BARE]} ]]; then
- echo "$use_path"
- else
- echo "use $use_path;"
- fi
-}
-
-_handle_no_use() {
- declare tried_index_new="$1"
-
- if [[ $tried_index_new != true ]]; then
- execute index --silent --new
- execute fu "${CONFIG[@]}"
- return $?
- elif [[ ${CONFIG[$PREFER_OWN]} == '--prefer-own' ]]; then
- CONFIG[$PREFER_OWN]=
- execute fu "${CONFIG[@]}"
- return $?
- else
- infof 'No match found for class "%s"\n' "$class_name" >&2
- [[ ${CONFIG[$JSON]} == '--json' ]] && printf '[]'
- fi
- return 1
-}
-
-_handle_multiple_uses() {
- if [[ ${CONFIG[$AUTO_PICK]} == '--auto-pick' ]]; then
- use_path="${possibilities[0]}"
-
- return 0
- elif [[ ${CONFIG[$BARE]} == '--bare' ]]; then
- use_path="$(printf '%s\n' "${possibilities[@]}")"
-
- return 0
- elif [[ ${CONFIG[$JSON]} == '--json' ]]; then
- use_path="$(
- for i in "${!possibilities[@]}"; do
- printf '"%s"' "${possibilities[$i]//\\/\\\\}"
- [[ $i -lt $((${#possibilities[@]}-1)) ]] && printf ','
- echo
- done
- )"
-
- return 0
- fi
-
- infof 'Multiple matches for class "%s", please pick one.\n' "$class_name"
>&2
- select match in "${possibilities[@]}"; do
- use_path="$match"
- break
- done < /dev/tty
-}
-
-addUseStatements() {
- declare -a needs=("$@")
- declare use_statements=''
- if [[ ${CONFIG[$JSON]} == '--json' ]]; then
- declare -i length="$((${#needs[@]}-1))" current=0
- echo '{'
- for needed in "${needs[@]}"; do
- printf '"%s": ' "$needed"
- execute fu --json "$needed" "${CONFIG[$PREFER_OWN]}"
"${CONFIG[$AUTO_PICK]}"
- [[ $((current++)) -lt $length ]] && printf ','
- echo
- done
- echo '}'
-
- return 0
- fi
-
- while IFS='' read -r line; do
- echo "$line"
-
- if [[ $line == namespace* ]]; then
- IFS='' read -r line && echo "$line"
-
- use_statements="$(
- for needed in "${needs[@]}"; do
- execute fu "$needed" "${CONFIG[$PREFER_OWN]}"
"${CONFIG[$AUTO_PICK]}"
- done | sort
- )"
-
- [[ -n $use_statements ]] && echo "$use_statements"
- fi
- done
-
- declare -i added_uses=0
- added_uses="$(echo -n "$use_statements" | wc -l)"
- [[ -n $use_statements ]] && ((added_uses++))
- info "$added_uses use statements added out of ${#needs[@]} needed types.
Types that were needed:" >&2
- infof ' - "%s"\n' "${needs[@]}" >&2
-}
-
-debug() {
- if [[ $DEBUG -ge 1 ]]; then
- echo "[DEBUG] => $1" >&2
- fi
-}
-
-# shellcheck disable=SC2059
-debugf() {
- if [[ $DEBUG -ge 1 ]]; then
- declare format_string="$1"
- shift
- printf "[DEBUG] => $format_string" "$@" >&2
- fi
-}
-
-info() {
- if [[ $INFO -eq 1 ]]; then
- echo "[INFO] => $1" >&2
- fi
-}
-
-# shellcheck disable=SC2059
-infof() {
- if [[ $INFO -eq 1 ]]; then
- declare format_string="$1"
- shift
- printf "[INFO] => $format_string" "$@" >&2
- fi
-}
-
-##
-# Functions for parameter parsing
-
-# Enum for config
-declare -gri CLASS_NAME=0
-declare -gri PREFER_OWN=1
-declare -gri AUTO_PICK=2
-declare -gri STDOUT=3
-declare -gri JSON=4
-declare -gri BARE=5
-declare -gri WORD=6
-declare -gri EXPAND_CLASSES=7
-declare -gri NO_CLASSES=8
-declare -gri NAMESPACE=9
-declare -gri CLASS_PATH=10
-declare -gri INDEX_DIFF=11
-declare -gri NO_VENDOR=12 # Keep this around as it might be used later on
-declare -gri INDEX_NEW=13
-declare -gri FILE=14
-
-handleArguments() {
- declare -p CONFIG &>>/dev/null || return 1
- declare command="$1"
- shift
- case "$command" in
- find-use)
- _handle_find_use_arguments "$@" || return $?
- ;;
- fix-uses)
- _handle_fix_uses_arguments "$@" || return $?
- ;;
- index)
- _handle_index_arguments "$@" || return $?
- ;;
- classes-in-namespace)
- _handle_classes_in_namespace_arguments "$@" || return $?
- ;;
- filepath)
- _handle_filepath_arguments "$@" || return $?
- ;;
- *)
- printf 'handleArguments (line %s): Unknown command "%s" passed.\n'
"$(caller)" "$command">&2
- return 1
- ;;
- esac
-}
-
-_handle_filepath_arguments() {
- declare arg="$1"
- while shift; do
- case "$arg" in
- -s | --silent)
- INFO=0
- ;;
- --*)
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- ;;
- -*)
- if [[ ${#arg} -gt 2 ]]; then
-
- declare -i i=1
- while [[ $i -lt ${#arg} ]]; do
- _handle_filepath_arguments "-${arg:$i:1}"
- ((i++))
- done
- else
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- fi
- ;;
- '')
- :
- ;;
- *)
- if [[ -n ${CONFIG[$CLASS_PATH]} ]]; then
- printf 'Unexpected argument: "%s"\n' "$arg" >&2
- return 1
- fi
- CONFIG[$CLASS_PATH]="$arg"
- esac
- arg="$1"
- done
-}
-
-_handle_classes_in_namespace_arguments() {
- declare arg="$1"
- while shift; do
- case "$arg" in
- -s | --silent)
- INFO=0
- ;;
- --*)
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- ;;
- -*)
- if [[ ${#arg} -gt 2 ]]; then
- declare -i i=1
- while [[ $i -lt ${#arg} ]]; do
- _handle_classes_in_namespace_arguments "-${arg:$i:1}"
- ((i++))
- done
- else
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- fi
- ;;
- '')
- :
- ;;
- *)
- if [[ -n ${CONFIG[$NAMESPACE]} ]]; then
- printf 'Unexpected argument: "%s"\n' "$arg" >&2
- return 1
- fi
- CONFIG[$NAMESPACE]="$arg"
- esac
- arg="$1"
- done
-}
-
-_handle_index_arguments() {
- declare arg="$1"
- while shift; do
- case "$arg" in
- -s | --silent)
- INFO=0
- ;;
- -d | --diff)
- CONFIG[$INDEX_DIFF]='--diff'
- ;;
- -N | --new)
- CONFIG[$INDEX_NEW]='--new'
- ;;
- --*)
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- ;;
- -*)
- if [[ ${#arg} -gt 2 ]]; then
- declare -i i=1
- while [[ $i -lt ${#arg} ]]; do
- _handle_index_arguments "-${arg:$i:1}"
- ((i++))
- done
- else
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- fi
- ;;
- *)
- printf 'Unexpected argument: "%s"\n' "$arg" >&2
- return 1
- ;;
- esac
- arg="$1"
- done
-}
-
-_handle_fix_uses_arguments() {
- declare arg="$1"
- while shift; do
- case "$arg" in
- -s | --silent)
- INFO=0
- ;;
- -p | --prefer-own)
- CONFIG[$PREFER_OWN]='--prefer-own'
- ;;
- -a | --auto-pick)
- CONFIG[$AUTO_PICK]='--auto-pick'
- ;;
- -o | --stdout)
- CONFIG[$STDOUT]='--stdout'
- INFO=0
- ;;
- -j | --json)
- CONFIG[$STDOUT]='--stdout'
- CONFIG[$JSON]='--json'
- ;;
- --*)
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- ;;
- -*)
- if [[ ${#arg} -gt 2 ]]; then
- declare -i i=1
- while [[ $i -lt ${#arg} ]]; do
- _handle_fix_uses_arguments "-${arg:$i:1}"
- ((i++))
- done
- else
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- fi
- ;;
- '')
- :
- ;;
- *)
- if [[ -n ${CONFIG[$FILE]} ]]; then
- printf 'Unexpected argument: "%s"\n' "$arg" >&2
- return 1
- fi
- CONFIG[$FILE]="$arg"
- esac
- arg="$1"
- done
-}
-
-# shellcheck disable=SC2034
-_handle_find_use_arguments() {
- declare arg="$1"
- while shift; do
- case "$arg" in
- -s | --silent)
- INFO=0
- ;;
- -b | --bare)
- CONFIG[$BARE]='--bare'
- ;;
- -p | --prefer-own)
- CONFIG[$PREFER_OWN]='--prefer-own'
- ;;
- -a | --auto-pick)
- CONFIG[$AUTO_PICK]='--auto-pick'
- ;;
- -j | --json) CONFIG[$STDOUT]='--stdout'
- CONFIG[$JSON]='--json'
- INFO=0
- ;;
- --*)
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- ;;
- -*)
- if [[ ${#arg} -gt 2 ]]; then
- declare -i i=1
- while [[ $i -lt ${#arg} ]]; do
- _handle_find_use_arguments "-${arg:$i:1}"
- ((i++))
- done
- else
- printf 'Unknown option: "%s"\n' "${arg}" >&2
- return 1
- fi
- ;;
- '')
- :
- ;;
- *)
- if [[ -n ${CONFIG[$CLASS_NAME]} ]]; then
- printf 'Unexpected argument: "%s"\n' "$arg" >&2
- return 1
- fi
- CONFIG[$CLASS_NAME]="$arg"
- esac
- arg="$1"
- done
-}
-
-##
-# This function outputs the difference between the files that are present in
the
-# index and the files that are present in the project directory. The output
format is:
-# +:NEW_FILE (**Not in index but exists on disk**)
-# -:DELETED_FILE (**In index but does not exist on disk**)
-##
-diffIndex() {
- diff --unchanged-line-format='' --new-line-format='+:%L'
--old-line-format='-:%L' \
- <(sort -u < "$INDEXED" | sed '/^[[:blank:]]*$/d') \
- <(find ./ -name '*.php' -type f | sed
's!^\./\|^./\(var\|.cache\|vendor/bin\)/.\+$!!g; /^[[:blank:]]*$/d' | sort)
-}
-
-##
-# This function reads the output of a grep command with the option -H or
-# --with-filename enabled. The lines containing class and namespace
declarations
-# will be parsed and added to the index.
-#
-# shellcheck disable=SC2153
-##
-fillIndex() {
- [[ -n $CACHE_DIR ]] || return 1
- [[ -n $CLASSES ]] || return 1
- [[ -n $NAMESPACES ]] || return 1
- [[ -n $USES ]] || return 1
- [[ -n $USES_LOOKUP ]] || return 1
- [[ -n $USES_LOOKUP_OWN ]] || return 1
- [[ -n $FILE_PATHS ]] || return 1
- [[ -n $NAMESPACE_FILE_PATHS ]] || return 1
- [[ -n $INDEXED ]] || return 1
-
- [[ -d $CACHE_DIR ]] || mkdir -p "$CACHE_DIR"
-
- # Clean up index files if not diffing.
- if [[ ${CONFIG[$INDEX_NEW]} != '--new' ]]; then
- echo > "$NAMESPACES"
- echo > "$CLASSES"
- echo > "$USES"
- echo > "$USES_LOOKUP"
- echo > "$FILE_PATHS"
- echo > "$USES_OWN"
- echo > "$USES_LOOKUP_OWN"
- echo > "$NAMESPACE_FILE_PATHS"
- echo > "$INDEXED"
- fi
-
- declare -A namespaces=() classes=()
- while IFS=':' read -ra line; do
- declare file="${line[0]}"
-
- # Save the namespace or class to add to the FQN cache later on.
- if [[ "${line[1]}" =~ (class|trait|interface)[[:blank:]]+([A-Za-z_]+)
]]; then
- classes[$file]="${BASH_REMATCH[2]}"
- elif [[ "${line[1]}" =~ namespace[[:blank:]]+([A-Za-z_\\]+) ]]; then
- namespaces[$file]="${BASH_REMATCH[1]}"
- else
- debugf 'No class or namespace found in line "%s"' "${line[0]}"
- fi
-
- # Add filename to file with indexed filenames. This is required
- # for diffing the index.
- echo "$file" >> "$INDEXED"
-
- if [[ $((++lines%500)) -eq 0 ]]; then
- info "indexed $lines lines."
- fi
- done
-
- # Fill up the index
- declare -i uses=0
- for file in "${!classes[@]}"; do
- declare namespace="${namespaces[$file]}"
- declare class="${classes[$file]}"
-
- if [[ -z $class ]]; then
- debugf 'Class is missing for file "%s"\n' "$file"
- debugf 'Namespace: "%s"\n' "$namespace"
- continue
- fi
-
- ((uses++))
- [[ $((uses%500)) -eq 0 ]] && info "Found FQN's for $uses classes."
-
- echo "$namespace" >> "$NAMESPACES"
- echo "$class" >> "$CLASSES"
- echo "$namespace\\$class" >> "$USES"
- echo "$class:$namespace\\$class" >> "$USES_LOOKUP"
- echo "$file:$namespace\\$class" >> "$FILE_PATHS"
- echo "$file:$namespace" >> "$NAMESPACE_FILE_PATHS"
-
- if [[ $file != 'vendor/'* ]]; then
- echo "$namespace\\$class" >> "$USES_OWN"
- echo "$class:$namespace\\$class" >> "$USES_LOOKUP_OWN"
- fi
-
- done
-
- # This keeps the index of class names unique, so that completing class
names takes as little
- # time as possible.
- # Use echo and a subshell here to prevent changing the file before the
command is done.
- # shellcheck disable=SC2005
- echo "$(sort -u < "$CLASSES")" > "$CLASSES"
-
- # Ditto for the namespaces index
- # shellcheck disable=SC2005
- echo "$(sort -u < "$NAMESPACES")" > "$NAMESPACES"
-
- info "Finished indexing. Indexed ${lines} lines and found FQN's for $uses
classes." >&2
-}
-
-checkCache() {
- if ! [[ -d "$CACHE_DIR" ]]; then
- info "No cache dir found, indexing." >&2
- execute index
- fi
-}
-
-##
-# Find use statements and needed classes in a file.
-
-findUsesAndNeeds() {
- declare -p needs &>>/dev/null || return 1
- declare -p uses &>>/dev/null || return 1
- # shellcheck disable=SC2154
- declare -p namespace &>>/dev/null || return 1
-
- while read -r line; do
- [[ $line == namespace* ]] && check_uses='true'
- if [[ $line == ?(@(abstract|final) )@(class|interface|trait)* ]]; then
- check_uses='false'
- check_needs='true'
-
- read -ra line_array <<<"$line"
- set -- "${line_array[@]}"
- while shift && [[ "$1" != @(extends|implements) ]]; do :; done;
- while shift && [[ -n $1 ]]; do
- [[ $1 == 'implements' ]] && shift
- [[ $1 == \\* ]] || _set_needed_if_not_used "$1"
- done
- fi
-
- if $check_uses; then
- if [[ $line == use* ]]; then
- declare class_name="${line##*\\}"
- [[ $class_name == *as* ]] && class_name="${class_name##*as }"
- debug "Class name: $class_name"
- class_name="${class_name%%[^a-zA-Z]*}"
- uses["$class_name"]='used'
- fi
- fi
-
- if $check_needs; then
- if [[ $line == *function*([[:space:]])*([[:alnum:]_])\(* ]]; then
- _check_function_needs "$line"
- continue
- fi
- _check_needs "$line"
- fi
- done
-}
-
-_check_function_needs() {
- # Strip everything up until function name and argument declaration.
- declare line="${1#*function}" function_declaration="${1#*function}"
-
- # Collect the entire argument declaration
- while [[ $line != *'{'* ]] && read -r line; do
- function_declaration="$function_declaration $line"
- done
-
- declare -a words=()
- read -ra words <<<"$function_declaration"
- for i in "${!words[@]}"; do
- if [[ "${words[$i]}" =~ ^'$'[a-zA-Z_]+ ]]; then
- declare prev_word="${words[$((i-1))]}"
- if [[ $prev_word =~ ^([^\(]*\()?([A-Za-z]+)$ ]]; then
- declare class_name="${BASH_REMATCH[2]}"
- debugf 'Found parameter type "%s" for function "%s"\n'
"$class_name" "$function_declaration"
- _set_needed_if_not_used "$class_name"
- fi
- fi
- done
- if [[ "$function_declaration" =~ \):[[:space:]]+([a-zA-Z]+) ]]; then
- declare class_name="${BASH_REMATCH[1]}"
- debugf 'Found return type "%s" for function "%s"\n' "$class_name"
"$function_declaration"
- _set_needed_if_not_used "$class_name"
- fi
-}
-
-_check_needs() {
- declare line="$1" match=''
- if _line_matches "$line"; then
- declare class_name="${match//[^a-zA-Z]/}"
-
- debugf 'Extracted type "%s" from line "%s". Entire match: "%s"\n'
"$class_name" "$line" "${BASH_REMATCH[0]}"
- _set_needed_if_not_used "$class_name"
-
- line="${line/"${BASH_REMATCH[0]/}"}"
- _check_needs "$line"
- fi
-}
-
-# shellcheck disable=SC2049
-_line_matches() {
- if [[ $line =~ 'new'[[:space:]]+([^\\][A-Za-z]+)\( ]] \
- || [[ $line =~ 'instanceof'[[:space:]]+([A-Za-z]+) ]] \
- || [[ $line =~ catch[[:space:]]*\(([A-Za-z]+) ]] \
- || [[ $line =~ \*[[:blank:]]*@([A-Z][a-zA-Z]*) ]]; then
- match="${BASH_REMATCH[1]}"
- return $?
- elif [[ $line =~ @(var|param|return|throws)[[:space:]]+([A-Za-z]+) ]] \
- || [[ $line =~ (^|[\(\[\{[:blank:]])([A-Za-z]+)'::' ]]; then
- match="${BASH_REMATCH[2]}"
- return $?
- fi
- return 1
-}
-
-_set_needed_if_not_used() {
- declare class_name="$1"
- if [[ -z ${uses["$class_name"]} ]] \
- && [[ -z ${namespace["$class_name"]} ]] \
- && [[ "$class_name" !=
@(static|self|string|int|float|array|object|bool|mixed|parent|void) ]]; then
- needs["$class_name"]='needed'
- fi
-}
-
-execute "$@"
diff --git a/phpinspect-index.el b/phpinspect-index.el
index dd164dca83..c952c4e625 100644
--- a/phpinspect-index.el
+++ b/phpinspect-index.el
@@ -42,7 +42,7 @@
(defun phpinspect-return-annotation-p (token)
(phpinspect-token-type-p token :return-annotation))
-(defun phpinspect--index-function-arg-list (type-resolver arg-list)
+(defun phpinspect--index-function-arg-list (type-resolver arg-list &optional
add-used-types)
(let ((arg-index)
(current-token)
(arg-list (cl-copy-list arg-list)))
@@ -51,7 +51,8 @@
(phpinspect-variable-p (car arg-list)))
(push `(,(cadr (pop arg-list))
,(funcall type-resolver (phpinspect--make-type :name
(cadr current-token))))
- arg-index))
+ arg-index)
+ (when add-used-types (funcall add-used-types (list (cadr
current-token)))))
((phpinspect-variable-p (car arg-list))
(push `(,(cadr (pop arg-list))
nil)
@@ -63,7 +64,12 @@
(or (not type)
(phpinspect--type= type phpinspect--object-type)))
-(defun phpinspect--index-function-from-scope (type-resolver scope
comment-before)
+(defun phpinspect--index-function-from-scope (type-resolver scope
comment-before &optional add-used-types)
+ "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.)."
(let* ((php-func (cadr scope))
(declaration (cadr php-func))
(type (if (phpinspect-word-p (car (last declaration)))
@@ -95,6 +101,12 @@
(phpinspect--make-type :name
return-annotation-type)))
(setf (phpinspect--type-collection type) t)))))
+ (when add-used-types
+ (let ((used-types (phpinspect--find-used-types-in-tokens
+ `(,(seq-find #'phpinspect-block-p php-func)))))
+ (when type (push (phpinspect--type-bare-name type) used-types))
+ (funcall add-used-types used-types)))
+
(phpinspect--make-function
:scope `(,(car scope))
:name (cadadr (cdr declaration))
@@ -102,7 +114,8 @@
phpinspect--null-type)
:arguments (phpinspect--index-function-arg-list
type-resolver
- (phpinspect-function-argument-list php-func)))))
+ (phpinspect-function-argument-list php-func)
+ add-used-types))))
(defun phpinspect--index-const-from-scope (scope)
(phpinspect--make-variable
@@ -141,7 +154,7 @@
(cadr class-token))))
(cadr subtoken)))
-(defun phpinspect--index-class (imports type-resolver class)
+(defun phpinspect--index-class (imports type-resolver location-resolver class)
"Create an alist with relevant attributes of a parsed class."
(phpinspect--log "INDEXING CLASS")
(let ((methods)
@@ -154,7 +167,15 @@
(class-name (phpinspect--get-class-name-from-token class))
;; Keep track of encountered comments to be able to use type
;; annotations.
- (comment-before))
+ (comment-before)
+ ;; The types that are used within the code of this class' methods.
+ (used-types)
+ (add-used-types))
+ (setq add-used-types
+ (lambda (additional-used-types)
+ (if used-types
+ (nconc used-types additional-used-types)
+ (setq used-types additional-used-types))))
;; Find out what the class extends or implements
(let ((enc-extends nil)
@@ -195,7 +216,8 @@
(push (phpinspect--index-function-from-scope
type-resolver
(list
(car token)
(cadadr token))
-
comment-before)
+
comment-before
+
add-used-types)
static-methods))
((phpinspect-variable-p (cadadr token))
@@ -208,14 +230,16 @@
(phpinspect--log "comment-before is: %s" comment-before)
(push (phpinspect--index-function-from-scope type-resolver
token
-
comment-before)
+ comment-before
+
add-used-types)
methods))))
((phpinspect-static-p token)
(cond ((phpinspect-function-p (cadr token))
(push (phpinspect--index-function-from-scope type-resolver
`(:public
,(cadr
token))
-
comment-before)
+ comment-before
+
add-used-types)
static-methods))
((phpinspect-variable-p (cadr token))
@@ -232,7 +256,8 @@
;; Bare functions are always public
(push (phpinspect--index-function-from-scope type-resolver
(list :public token)
- comment-before)
+ comment-before
+ add-used-types)
methods))
((phpinspect-doc-block-p token)
(phpinspect--log "setting comment-before %s" token)
@@ -270,6 +295,7 @@
`(,class-name .
(phpinspect--indexed-class
(class-name . ,class-name)
+ (location . ,(funcall location-resolver class))
(imports . ,imports)
(methods . ,methods)
(static-methods . ,static-methods)
@@ -277,7 +303,9 @@
(variables . ,variables)
(constants . ,constants)
(extends . ,extends)
- (implements . ,implements))))))
+ (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.
@@ -286,18 +314,19 @@ Accounts for namespaces that are defined with '{}'
blocks."
(cdaddr namespace)
(cdr namespace)))
-(defun phpinspect--index-classes (imports classes &optional namespace indexed)
+(defun phpinspect--index-classes
+ (imports classes type-resolver-factory location-resolver &optional
namespace indexed)
"Index the class tokens in `classes`, using the imports in `imports`
as Fully Qualified names. `namespace` will be assumed the root
namespace if not provided"
(if classes
(let ((class (pop classes)))
(push (phpinspect--index-class
- imports
- (phpinspect--make-type-resolver imports class namespace)
- class)
+ imports (funcall type-resolver-factory imports class namespace)
+ location-resolver class)
indexed)
- (phpinspect--index-classes imports classes namespace indexed))
+ (phpinspect--index-classes imports classes type-resolver-factory
+ location-resolver namespace indexed))
(nreverse indexed)))
(defun phpinspect--use-to-type (use)
@@ -316,33 +345,82 @@ namespace if not provided"
(defun phpinspect--uses-to-types (uses)
(mapcar #'phpinspect--use-to-type uses))
-(defun phpinspect--index-namespace (namespace)
+(defun phpinspect--index-namespace (namespace type-resolver-factory
location-resolver)
(phpinspect--index-classes
(phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace))
(seq-filter #'phpinspect-class-p namespace)
- (cadadr namespace)))
+ type-resolver-factory location-resolver (cadadr namespace) nil))
-(defun phpinspect--index-namespaces (namespaces &optional indexed)
+(defun phpinspect--index-namespaces
+ (namespaces type-resolver-factory location-resolver &optional indexed)
(if namespaces
(progn
- (push (phpinspect--index-namespace (pop namespaces)) indexed)
- (phpinspect--index-namespaces namespaces indexed))
+ (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.")
-(defun phpinspect--index-tokens (tokens)
+(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))
+ (while tokens
+ (let ((token (pop tokens))
+ (previous-token (car previous-tokens)))
+ (cond ((and (phpinspect-word-p previous-token)
+ (string= "new" (cadr previous-token))
+ (phpinspect-word-p token))
+ (let ((type (cadr token)))
+ (when (not (string-match-p "\\\\" type))
+ (push type used-types))))
+ ((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))))
+ ((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)))))
+ ((or (phpinspect-list-p token) (phpinspect-block-p token))
+ (setq used-types (append (phpinspect--find-used-types-in-tokens
(cdr token))
+ used-types))))
+
+ (push token previous-tokens)))
+ 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))
+ (phpinspect--index-namespaces (seq-filter
#'phpinspect-namespace-p tokens)
+ type-resolver-factory
+ location-resolver)
(phpinspect--index-classes
imports
- (seq-filter #'phpinspect-class-p tokens))))
+ (seq-filter #'phpinspect-class-p tokens)
+ type-resolver-factory
+ location-resolver)))
+ ,(append '(used-types)
+ (phpinspect--find-used-types-in-tokens tokens))
(functions))
;; TODO: Implement function indexation
))
@@ -381,10 +459,5 @@ namespace if not provided"
(phpinspect--log "Failed to find file for type %s: %s" type error)
nil)))
-(defsubst phpinspect--index-thread-enqueue (task)
- (phpinspect--queue-enqueue-noduplicate phpinspect--index-queue
- task
- #'phpinspect--index-task=))
-
(provide 'phpinspect-index)
;;; phpinspect-index.el ends here
diff --git a/phpinspect-parser.el b/phpinspect-parser.el
index 9d2609bcbc..8471a908e1 100644
--- a/phpinspect-parser.el
+++ b/phpinspect-parser.el
@@ -384,13 +384,26 @@ token is \";\", which marks the end of a statement in
PHP."
(cond ,@(mapcar
(lambda (handler)
`((looking-at ,(plist-get (symbol-value handler)
'regexp))
- (let ((token (funcall ,(symbol-function handler)
+ (let ((start-position (point))
+ (token (funcall ,(symbol-function handler)
(match-string 0)
max-point)))
(when token
(if (null tokens)
(setq tokens (list token))
- (nconc tokens (list token)))))))
+ (progn
+ (nconc tokens (list token))))
+
+ ;; When parsing within a buffer that has
+ ;; `phpinspect-current-buffer` set, update
the
+ ;; token location map. Usually, this variable
+ ;; is set when `phpinspect-mode` is active.
+ (when phpinspect-current-buffer
+ (puthash token
+ (phpinspect-make-region
start-position
+ (point))
+ (phpinspect-buffer-location-map
+ phpinspect-current-buffer)))))))
handlers)
(t (forward-char))))
(push ,tree-type tokens))))))
diff --git a/phpinspect-project.el b/phpinspect-project.el
index 26e12cac95..0f73122f2b 100644
--- a/phpinspect-project.el
+++ b/phpinspect-project.el
@@ -25,13 +25,25 @@
(require 'phpinspect-class)
(require 'phpinspect-type)
+(require 'phpinspect-fs)
+(require 'filenotify)
-(cl-defstruct (phpinspect--project (:constructor
phpinspect--make-project-cache))
+(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
@@ -39,12 +51,35 @@ indexed classes in the project")
(root nil
:type string
:documentation
- "The root directory of this project"))
+ "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.")
+(cl-defmethod phpinspect--project-purge ((project phpinspect--project))
+ "Disable all background processes for project and put it in a `purged`
state."
+ (maphash (lambda (_ watcher) (file-notify-rm-watch watcher))
+ (phpinspect--project-file-watchers project))
+
+ (setf (phpinspect--project-file-watchers project)
+ (make-hash-table :test #'equal :size 10000 :rehash-size 10000))
+ (setf (phpinspect--project-purged project) t))
+
+(cl-defmethod phpinspect--project-watch-file ((project phpinspect--project)
+ filepath
+ callback)
+ (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)
@@ -61,16 +96,15 @@ indexed classes in the project")
(cl-defmethod phpinspect--project-enqueue-if-not-present
((project phpinspect--project) (type phpinspect--type))
- (let ((class (phpinspect--project-get-class project type)))
- (when (or (not class)
- (not (or (phpinspect--class-initial-index class)
- (phpinspect--class-index-queued class))))
- (when (not class)
- (setq class (phpinspect--project-create-class project type)))
- (phpinspect--log "Adding unpresent class %s to index queue" type)
- (setf (phpinspect--class-index-queued class) t)
- (phpinspect-worker-enqueue (phpinspect--project-worker project)
- (phpinspect-make-index-task project 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))))))
(cl-defmethod phpinspect--project-add-class-attribute-types-to-index-queue
((project phpinspect--project) (class phpinspect--class))
diff --git a/phpinspect-type.el b/phpinspect-type.el
index c197cea9ec..550d22d50f 100644
--- a/phpinspect-type.el
+++ b/phpinspect-type.el
@@ -104,6 +104,7 @@ See https://wiki.php.net/rfc/static_return_type ."
(car (last (split-string fqn "\\\\"))))
(cl-defmethod phpinspect--type-bare-name ((type phpinspect--type))
+ "Return just the name, without namespace part, of TYPE."
(phpinspect--get-bare-class-name-from-fqn (phpinspect--type-name type)))
(cl-defmethod phpinspect--type= ((type1 phpinspect--type) (type2
phpinspect--type))
@@ -145,7 +146,6 @@ NAMESPACE may be nil, or a string with a namespace FQN."
(defun phpinspect--make-type-resolver (types &optional token-tree namespace)
"Little wrapper closure to pass around and resolve types with."
-
(let* ((inside-class
(if token-tree (or (phpinspect--find-innermost-incomplete-class
token-tree)
(phpinspect--find-class-token token-tree))))
diff --git a/phpinspect-worker.el b/phpinspect-worker.el
index 5945245ea7..f63d97b0a0 100644
--- a/phpinspect-worker.el
+++ b/phpinspect-worker.el
@@ -31,6 +31,18 @@
(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
@@ -210,6 +222,18 @@ on the worker independent of dynamic variables during
testing.")
(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))))
+
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-dynamic-worker)
task)
(phpinspect-worker-enqueue (phpinspect-resolve-dynamic-worker worker)
task))
@@ -241,7 +265,10 @@ CONTINUE must be a condition-variable"
(mx (make-mutex))
(continue (make-condition-variable mx)))
(if task
- (phpinspect-task-execute task worker)
+ ;; 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))
@@ -292,24 +319,18 @@ CONTINUE must be a condition-variable"
(interactive)
(phpinspect-worker-stop phpinspect-worker))
-(cl-defstruct (phpinspect-index-task
- (:constructor phpinspect-make-index-task-generated))
- "Represents an index task that can be executed by a `phpinspect-worker`."
- (project nil
- :type phpinspect--project
- :documentation
- "The project that the task should be executed for.")
- (type nil
- :type phpinspect--type
- :documentation
- "The type whose file should be indexed."))
-
(cl-defgeneric phpinspect-make-index-task ((project phpinspect--project)
(type phpinspect--type))
(phpinspect-make-index-task-generated
:project project
:type type))
+(cl-defgeneric phpinspect-task-project (task)
+ "The project that this task belongs to.")
+
+(cl-defmethod phpinspect-task-project ((task phpinspect-index-task))
+ (phpinspect-index-task-project task))
+
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
@@ -323,19 +344,19 @@ CONTINUE must be a condition-variable"
(phpinspect-index-task-type task)
(phpinspect--project-root project))
- (if is-native-type
- (progn
- (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))
- (let* ((type (phpinspect-index-task-type task))
- (root-index (phpinspect--index-type-file project type)))
- (when root-index
- (phpinspect--project-add-index project root-index))))))
+ (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--index-type-file project type)))
+ (when root-index
+ (phpinspect--project-add-index project root-index)))))))
(provide 'phpinspect-worker)
diff --git a/phpinspect.el b/phpinspect.el
index 0e39cb0cda..fb1b59012c 100644
--- a/phpinspect.el
+++ b/phpinspect.el
@@ -1,4 +1,4 @@
-;; phpinspect.el --- PHP parsing and completion package -*- lexical-binding:
t; -*-
+; phpinspect.el --- PHP parsing and completion package -*- lexical-binding:
t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
@@ -38,6 +38,9 @@
(require 'phpinspect-index)
(require 'phpinspect-class)
(require 'phpinspect-worker)
+(require 'phpinspect-autoload)
+(require 'phpinspect-imports)
+(require 'phpinspect-buffer)
(defvar-local phpinspect--buffer-index nil
"The result of the last successfull parse + index action
@@ -67,16 +70,6 @@ phpinspect")
(defvar phpinspect-eldoc-word-width 14
"The maximum width of words in eldoc strings.")
-(defvar phpinspect-index-executable
- (concat (file-name-directory
- (or load-file-name
- buffer-file-name))
- "/phpinspect-index.bash")
- "The path to the exexutable file that indexes class file names.
-Should normally be set to \"phpinspect-index.bash\" in the source
- file directory.")
-
-
(cl-defstruct (phpinspect--completion
(:constructor phpinspect--construct-completion))
"Contains a possible completion value with all it's attributes."
@@ -708,7 +701,7 @@ more recent"
(defun phpinspect--init-mode ()
"Initialize the phpinspect minor mode for the current buffer."
-
+ (setq phpinspect-current-buffer (phpinspect-make-buffer :buffer
(current-buffer)))
(make-local-variable 'company-backends)
(add-to-list 'company-backends #'phpinspect-company-backend)
@@ -755,6 +748,7 @@ users will have to use \\[phpinspect-purge-cache]."
(defun phpinspect--disable-mode ()
"Clean up the buffer environment for the mode to be disabled."
+ (setq phpinspect-current-buffer nil)
(kill-local-variable 'phpinspect--buffer-project)
(kill-local-variable 'phpinspect--buffer-index)
(kill-local-variable 'company-backends)
@@ -784,8 +778,8 @@ For finding/opening class files see
`phpinspect-find-class-file' (bound to \\[phpinspect-find-class-file]).
To automatically add missing use statements for used classes to a
-visited file, use `phpinspect-fix-uses-interactive'
-(bound to \\[phpinspect-fix-uses-interactive]].)
+visited file, use `phpinspect-fix-imports'
+(bound to \\[phpinspect-fix-imports]].)
Example configuration:
@@ -806,7 +800,7 @@ Example configuration:
(setq-local company-backends '(phpinspect-company-backend))
;; Shortcut to add use statements for classes you use.
- (define-key php-mode-map (kbd \"C-c u\") 'phpinspect-fix-uses-interactive)
+ (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
@@ -1094,6 +1088,13 @@ If its value is nil, it is created and then returned."
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)))
+
+ ;; Assign a fresh cache object
(setq phpinspect-cache (phpinspect--make-cache)))
(defun phpinspect--locate-dominating-project-file (start-file)
@@ -1146,69 +1147,26 @@ level of START-FILE in stead of `default-directory`."
(json-key-type 'string))
,@body))
-;; Use statements
-;;;###autoload
-(defun phpinspect-fix-uses-interactive ()
- "Add missing use statements to the currently visited PHP file."
- (interactive)
- (let ((project-root (phpinspect-project-root)))
- (when project-root
- (save-buffer)
- (let* ((phpinspect-json (shell-command-to-string
- (format "cd %s && %s fxu --json %s"
- (shell-quote-argument
project-root)
- (shell-quote-argument
phpinspect-index-executable)
- (shell-quote-argument
buffer-file-name)))))
- (let* ((json-object-type 'hash-table)
- (json-array-type 'list)
- (json-key-type 'string)
- (phpinspect-json-data (json-read-from-string
phpinspect-json)))
- (maphash #'phpinspect-handle-phpinspect-json
phpinspect-json-data))))))
-
-(defun phpinspect-handle-phpinspect-json (class-name candidates)
- "Handle key value pair of classname and FQN's"
- (let ((ncandidates (length candidates)))
- (cond ((= 1 ncandidates)
- (phpinspect-add-use (pop candidates)))
- ((= 0 ncandidates)
- (message "No use statement found for class \"%s\"" class-name))
- (t
- (phpinspect-add-use (completing-read "Class: " candidates))))))
-
-;; TODO: Implement this using the parser in stead of regexes.
-(defun phpinspect-add-use (fqn) "Add use statement to a php file"
- (save-excursion
- (let ((current-char (point)))
- (goto-char (point-min))
- (cond
- ((re-search-forward "^use" nil t) (forward-line 1))
- ((re-search-forward "^namespace" nil t) (forward-line 2))
- ((re-search-forward
- "^\\(abstract \\|/\\* final \\*/ ?\\|final
\\|\\)\\(class\\|trait\\|interface\\)"
- nil )
- (forward-line -1)
- (phpinspect-goto-first-line-no-comment-up)))
-
- (insert (format "use %s;%c" fqn ?\n))
- (goto-char current-char))))
-
-(defun phpinspect-goto-first-line-no-comment-up ()
- "Go up until a line is encountered that does not start with a comment."
- (when (string-match "^\\( ?\\*\\|/\\)" (thing-at-point 'line t))
- (forward-line -1)
- (phpinspect-goto-first-line-no-comment-up)))
-
(defsubst phpinspect-insert-file-contents (&rest args)
"Call `phpinspect-insert-file-contents-function' with ARGS as arguments."
(apply phpinspect-insert-file-contents-function args))
-(defun phpinspect-get-all-fqns (&optional fqn-file)
- (unless fqn-file
- (setq fqn-file "uses"))
- (with-temp-buffer
- (phpinspect-insert-file-contents
- (concat (phpinspect-project-root) "/.cache/phpinspect/" fqn-file))
- (split-string (buffer-string) (char-to-string ?\n))))
+(defun phpinspect-get-all-fqns (&optional filter)
+ "Return a list of all FQNS congruent with FILTER in the currently active
project.
+
+FILTER must be nil or the symbol 'own' if FILTER is 'own', only
+fully qualified names from the project's source, and not its
+dependencies, are returned."
+ (let* ((project (phpinspect--cache-get-project-create
+ (phpinspect--get-or-create-global-cache)
+ (phpinspect-project-root)))
+ (autoloader (phpinspect--project-autoload project)))
+ (let ((fqns))
+ (maphash (lambda (type _) (push (symbol-name type) fqns))
+ (if (eq 'own filter)
+ (phpinspect-autoloader-own-types autoloader)
+ (phpinspect-autoloader-types autoloader)))
+ fqns)))
;;;###autoload
(defun phpinspect-find-class-file (fqn)
@@ -1230,7 +1188,7 @@ available FQNs for classes in the current project, which
aren't
located in \"vendor\" folder."
(interactive (list (phpinspect--make-type
:name
- (completing-read "Class: " (phpinspect-get-all-fqns
"uses_own")))))
+ (completing-read "Class: " (phpinspect-get-all-fqns
'own)))))
(find-file (phpinspect-type-filepath fqn)))
(defsubst phpinspect-type-filepath (fqn)
@@ -1242,35 +1200,22 @@ located in \"vendor\" folder."
when INDEX-NEW is non-nil, new files are added to the index
before the search is executed."
- (when (eq index-new 'index-new)
- (with-temp-buffer
- (call-process phpinspect-index-executable nil (current-buffer) nil
"index" "--new")))
- (let* ((default-directory (phpinspect-project-root))
- (result (with-temp-buffer
- (phpinspect--log "dir: %s" default-directory)
- (phpinspect--log "class: %s" (string-remove-prefix
- "\\"
- (phpinspect--type-name
class)))
- (list (call-process phpinspect-index-executable
- nil
- (current-buffer)
- nil
- "fp" (string-remove-prefix
- "\\"
- (phpinspect--type-name class)))
- (buffer-string)))))
- (if (not (= (car result) 0))
- (progn
- (phpinspect--log "Got non-zero return value %d Retrying with
reindex. output: \"%s\""
- (car result)
- (cadr result))
+ (let* ((project (phpinspect--cache-get-project-create
+ (phpinspect--get-or-create-global-cache)
+ (phpinspect-project-root)))
+ (autoloader (phpinspect--project-autoload project)))
+ (when (eq index-new 'index-new)
+ (phpinspect-autoloader-refresh autoloader))
+ (let* ((result (phpinspect-autoloader-resolve autoloader
(phpinspect--type-name-symbol class))))
+ (if (not result)
;; Index new files and try again if not done already.
(if (eq index-new 'index-new)
nil
- (phpinspect-get-class-filepath class 'index-new)))
- (concat (string-remove-suffix "/" default-directory)
- "/"
- (string-remove-prefix "/" (string-trim (cadr result)))))))
+ (progn
+ (phpinspect--log "Failed finding filepath for type %s. Retrying
with reindex."
+ (phpinspect--type-name class))
+ (phpinspect-get-class-filepath class 'index-new)))
+ result))))
(defun phpinspect-unique-strings (strings)
(seq-filter
@@ -1283,22 +1228,17 @@ before the search is executed."
strings))
(defun phpinspect-index-current-project ()
- "Index all available FQNs in the current project.
-
-Index is stored in files in the .cache directory of
-the project root."
+ "Index all available FQNs in the current project."
(interactive)
- (let* ((default-directory (phpinspect-project-root)))
- (with-current-buffer (get-buffer-create "**phpinspect-index**")
- (goto-char (point-max))
- (make-process
- :command `(,phpinspect-index-executable "index")
- :name "phpinspect-index-current-project"
- :buffer (current-buffer))
-
- (display-buffer (current-buffer) `(display-buffer-at-bottom
(window-height . 10)))
- (set-window-point (get-buffer-window (current-buffer) nil)
- (point-max)))))
+ (let* ((project (phpinspect--cache-get-project-create
+ (phpinspect--get-or-create-global-cache)
+ (phpinspect-project-root)))
+ (autoloader (phpinspect--project-autoload project)))
+ (phpinspect-autoloader-refresh autoloader)
+ (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)))))
(defun phpinspect-unique-lines ()
(let ((unique-lines (phpinspect-unique-strings (split-string (buffer-string)
"\n" nil nil))))
diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el
index 4c4ba1c908..0c48f2c1ee 100644
--- a/test/phpinspect-test.el
+++ b/test/phpinspect-test.el
@@ -1,4 +1,4 @@
-;;; phpinspect-test.el --- Unit tests for phpinslect.el -*- lexical-binding:
t; -*-
+;;; phpinspect-test.el --- Unit tests for phpinspect.el -*- lexical-binding:
t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
@@ -500,6 +500,10 @@ class Thing
(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-fs.el"))
+(load-file (concat phpinspect-test-directory "/test-project.el"))
+(load-file (concat phpinspect-test-directory "/test-buffer.el"))
+(load-file (concat phpinspect-test-directory "/test-index.el"))
+
(provide 'phpinspect-test)
;;; phpinspect-test.el ends here
diff --git a/test/test-buffer.el b/test/test-buffer.el
new file mode 100644
index 0000000000..ed36ae522f
--- /dev/null
+++ b/test/test-buffer.el
@@ -0,0 +1,68 @@
+;;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; Author: Hugo Thunnissen <devel@hugot.nl>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'phpinspect-parser)
+(require 'phpinspect-buffer)
+
+(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))
+ (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 (= 417 (phpinspect-region-start class-region)))
+ (should (= 2173 (phpinspect-region-end class-region)))
+
+ (should (= 423 (phpinspect-region-start classname-region)))
+ (should (= 440 (phpinspect-region-end classname-region))))))
+
+(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)))
+
+ (should (cdr parsed))))
diff --git a/test/test-index.el b/test/test-index.el
new file mode 100644
index 0000000000..e254ac4c8f
--- /dev/null
+++ b/test/test-index.el
@@ -0,0 +1,103 @@
+;;; test-index.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; Author: Hugo Thunnissen <devel@hugot.nl>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'ert)
+(require 'phpinspect-index)
+
+(ert-deftest phpinspect-index-static-methods ()
+ (let* ((class-tokens
+ `(:root
+ (:class
+ (:declaration (:word "class") (:word "Potato"))
+ (:block
+ (:static
+ (:function (:declaration (:word "function")
+ (:word "staticMethod")
+ (:list (:variable "untyped")
+ (:comma)
+ (:word "array")
+ (:variable "things")))
+ (:block)))))))
+ (index (phpinspect--index-tokens class-tokens))
+ (expected-index
+ `(phpinspect--root-index
+ (imports)
+ (classes
+ (,(phpinspect--make-type :name"\\Potato" :fully-qualified t)
+ phpinspect--indexed-class
+ (class-name . ,(phpinspect--make-type :name "\\Potato"
:fully-qualified t))
+ (location . (0 0))
+ (imports)
+ (methods)
+ (static-methods . (,(phpinspect--make-function
+ :name "staticMethod"
+ :scope '(:public)
+ :arguments `(("untyped" nil)
+ ("things"
,(phpinspect--make-type :name "\\array"
+
:fully-qualified t)))
+ :return-type phpinspect--null-type)))
+ (static-variables)
+ (variables)
+ (constants)
+ (extends)
+ (implements)
+ (used-types)))
+ (used-types)
+ (functions))))
+ (should (equal expected-index index))))
+
+(ert-deftest phpinspect-index-used-types-in-class ()
+ (let* ((result (phpinspect--index-tokens
+ (phpinspect-parse-string
+ "<?php namespace Field; class Potato {
+public function makeThing(): Thing
+{
+if ((new Monkey())->tree() === true) {
+ return new ExtendedThing();
+}
+return StaticThing::create(new ThingFactory())->makeThing((((new
Potato())->antiPotato(new OtherThing()))));
+}")))
+ (used-types (alist-get 'used-types (car (alist-get 'classes
result)))))
+ (should (equal
+ (mapcar #'phpinspect-intern-name
+ (sort
+ '("Monkey" "ExtendedThing" "StaticThing" "Thing"
"ThingFactory" "Potato" "OtherThing")
+ #'string<))
+ (sort used-types (lambda (s1 s2) (string< (symbol-name s1)
(symbol-name s2))))))))
+
+(ert-deftest phpinspect--find-used-types-in-tokens ()
+ (let ((blocks `(
+ ((:block (:word "return")
+ (:word "new")
+ (:word "Response")
+ (:list))
+ ("Response"))
+ ((:block (:list (:word "new") (:word "Response"))
+ (:object-attrib (:word "someMethod")
+ (:list (:word "new")
+ (:word "Request"))))
+ ("Request" "Response")))))
+ (dolist (set blocks)
+ (let ((result (phpinspect--find-used-types-in-tokens (car set))))
+ (should (equal (cadr set) result))))))
diff --git a/test/test-project.el b/test/test-project.el
new file mode 100644
index 0000000000..fa0ee2701e
--- /dev/null
+++ b/test/test-project.el
@@ -0,0 +1,46 @@
+;; test-project.el --- Unit tests for phpinspect.el -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; Author: Hugo Thunnissen <devel@hugot.nl>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'phpinspect-project)
+
+(ert-deftest phpinspect-project-purge ()
+ (let ((project (phpinspect--make-project)))
+ (phpinspect--project-purge project)
+
+ (should (eq t (phpinspect--project-purged project)))))
+
+(ert-deftest phpinspect-project-watch-file-and-purge ()
+ (let* ((root (make-temp-file "phpinspect-test" 'dir))
+ (fs (phpinspect-make-fs))
+ (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-purge project)
+
+ (should (= 0 (length (hash-table-values (phpinspect--project-file-watchers
project)))))))
- [elpa] externals/phpinspect 0ca527dbbd 070/126: Adapt `phpinspect-purge-parser-cache' to new parser caching approach, (continued)
- [elpa] externals/phpinspect 0ca527dbbd 070/126: Adapt `phpinspect-purge-parser-cache' to new parser caching approach, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 6627f6f76f 073/126: Remove commented parser code, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect e270729e14 088/126: Implement splay tree for overlay storage/lookup, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 94d5b75455 107/126: Add `phpinspect-pipeline-pause-time', ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect e8f486f095 013/126: Improve codestyle and documentation + add tests for indexation, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect c0786db131 040/126: WIP: Index every possibly required type ahead of time., ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 2e487e7810 039/126: Fix resolving of function argument types, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 2fd575dbf5 044/126: Add drone.yml, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect f013b3c709 036/126: WIP: Support ambiguous typehints, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect ef9a7336cf 049/126: Replace virtual-directory with more general virtual-fs implementation, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect dbf0ec0390 051/126: Transition from index script to autoloader,
ELPA Syncer <=
- [elpa] externals/phpinspect 97377c2922 055/126: Fix bugs in phpinspect-fix-imports, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 281c5e4ae6 077/126: Remove some overly verbose logging, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 0596bc52bf 091/126: Optimize splay tree and use it to store token's children, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect ab6954faf5 090/126: Retrieve and wrap metadata using the correct overlay for region, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 389e77eb8b 092/126: Expand existing overlay when possible, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 23245d0158 098/126: Fix some compilation warnings, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect db370623da 108/126: Implement "files" autoload strategy, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 6678ba20c6 103/126: Implement async processing pipeline, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 2099abced8 099/126: Add Cask configuration and fix some compilation warnings, ELPA Syncer, 2023/08/12
- [elpa] externals/phpinspect 3175d9a6ac 126/126: Fix typo, ELPA Syncer, 2023/08/12