|
|
@ -125,6 +125,146 @@ Elapsed time: 0.013878 (0.000000 in 0 GC’s)
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Development
|
|
|
|
## Development
|
|
|
|
|
|
|
|
The codebase of phpinspect is relatively large. As a new contributor, you'll
|
|
|
|
|
|
|
|
probably feel a bit lost in the many files, functions and datatypes of the
|
|
|
|
|
|
|
|
package. Let me tell you a bit about phpinspect's architecture.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Domains
|
|
|
|
|
|
|
|
There are roughly four main domains under which code in phpinspect can fall:
|
|
|
|
|
|
|
|
parsing, indexation, interpetation and user-facing functions. Below is a short
|
|
|
|
|
|
|
|
explanation of each domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### Parsing
|
|
|
|
|
|
|
|
The parser provides the underpinnings for almost all of phpinspect's
|
|
|
|
|
|
|
|
functionalities. It is implemented with specialized macros and supports two main
|
|
|
|
|
|
|
|
parsing modes: bare and incremental. The parser code is located in
|
|
|
|
|
|
|
|
`phpinspect-parser.el`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Bare Parsing
|
|
|
|
|
|
|
|
The parser produces a parse result in the form of a basic list, where each
|
|
|
|
|
|
|
|
parsed token is a list itself. The car of each list contains a keyword,
|
|
|
|
|
|
|
|
describing its meaning in the parsed code. Below is a lisp form and its
|
|
|
|
|
|
|
|
resulting insertion to demonstrate the structure of a syntax tree produced by
|
|
|
|
|
|
|
|
the bare parser.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`(insert (pp-to-string (phpinspect-parse-string "function foo(Bar $bar) { return $bar->baz; }")))`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```elisp
|
|
|
|
|
|
|
|
(:root
|
|
|
|
|
|
|
|
(:function
|
|
|
|
|
|
|
|
(:declaration
|
|
|
|
|
|
|
|
(:word "function")
|
|
|
|
|
|
|
|
(:word "foo")
|
|
|
|
|
|
|
|
(:list
|
|
|
|
|
|
|
|
(:word "Bar")
|
|
|
|
|
|
|
|
(:variable "bar")))
|
|
|
|
|
|
|
|
(:block
|
|
|
|
|
|
|
|
(:word "return")
|
|
|
|
|
|
|
|
(:variable "bar")
|
|
|
|
|
|
|
|
(:object-attrib
|
|
|
|
|
|
|
|
(:word "baz"))
|
|
|
|
|
|
|
|
(:terminator ";"))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Incremental Parsing
|
|
|
|
|
|
|
|
Bare parsing is fast, because it makes use of simple lisp datastructures and
|
|
|
|
|
|
|
|
keeps the parser logic simple. A downside of this parsing mode is that the
|
|
|
|
|
|
|
|
result lacks any extra metadata about the tokens, like what their start- and
|
|
|
|
|
|
|
|
endpoints are in a buffer. It also makes it hard to partially update the tree
|
|
|
|
|
|
|
|
after a buffer has been edited. As both are essential for a program that needs
|
|
|
|
|
|
|
|
to work efficiently in live-edited buffers, the incremental parser was
|
|
|
|
|
|
|
|
implemented. When parsing incrementally, the parser still produces the same
|
|
|
|
|
|
|
|
structure of nested lists as a result. But it also stores metadata about each
|
|
|
|
|
|
|
|
parsed token in an n-ary tree of `phpinspect-meta` objects (see
|
|
|
|
|
|
|
|
`phpinspect-meta.el`). The incremental parser is able to partially update an
|
|
|
|
|
|
|
|
existing tree after code has been edited, making it efficient for live buffers.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Parsers Summarised
|
|
|
|
|
|
|
|
The incremental parsing is efficient for use with live edited buffers. Due to
|
|
|
|
|
|
|
|
its higher complexity it is not as fast at parsing entire files as the bare
|
|
|
|
|
|
|
|
parser. For this reason, both the bare and the incremental parser are used for
|
|
|
|
|
|
|
|
what they are best at.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### Indexation
|
|
|
|
|
|
|
|
Within phpinspect, indexation is referred to as the process of extracting
|
|
|
|
|
|
|
|
information from parsed code and project configuration files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Code Indexation
|
|
|
|
|
|
|
|
The code in
|
|
|
|
|
|
|
|
`phpinspect-index.el` consumes bare syntax trees and extracts information about
|
|
|
|
|
|
|
|
PHP functions and types from them. Code in `phpinspect-buffer.el` implements the
|
|
|
|
|
|
|
|
same functionality, but for live buffers based on the data produced by the
|
|
|
|
|
|
|
|
incremental parser.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Project Indexation
|
|
|
|
|
|
|
|
Code in `phpinspect-autoload.el` implements logic for `psr-4`, `psr-0` and
|
|
|
|
|
|
|
|
`files` autoload strategies. It is able to read a composer.json file and
|
|
|
|
|
|
|
|
generate autoload information based on which a project's types and functions can
|
|
|
|
|
|
|
|
be found.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Caching
|
|
|
|
|
|
|
|
Code in `phpinspect-cache.el` and `phpinspect-class.el` allows phpinspect to
|
|
|
|
|
|
|
|
store the result of code indexations in memory, so that they can be accessed
|
|
|
|
|
|
|
|
instantly when a user requests information about their code.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Threading
|
|
|
|
|
|
|
|
Indexation takes time. Because emacs is single-threaded, you might think that
|
|
|
|
|
|
|
|
indexing a project would lock up your emacs for a while. Do not despair!
|
|
|
|
|
|
|
|
`phpinspect-worker.el` contains code for a worker with a job queue that uses
|
|
|
|
|
|
|
|
collaborative threads to do work when emacs is idle. When you start interacting
|
|
|
|
|
|
|
|
with emacs, it will back off and let you do your thing! `phpinspect-pipeline.el`
|
|
|
|
|
|
|
|
contains code for something similar to generators in PHP, combined with
|
|
|
|
|
|
|
|
collaborative threading. Pipelines are used for more intense processes that
|
|
|
|
|
|
|
|
should be completed with a bit more of a hurry. Emacs should remain responsive
|
|
|
|
|
|
|
|
while a pipeline is running, but there may be a slightly noticeable sluggishness
|
|
|
|
|
|
|
|
while they run. At the moment of writing, pipelines are mainly used to refresh a
|
|
|
|
|
|
|
|
projects autoloader.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### Interpretation
|
|
|
|
|
|
|
|
Having parsing, indexation and caching infrastructure is all well and good, but
|
|
|
|
|
|
|
|
how to we determine what information is actually useful to show to a user? This
|
|
|
|
|
|
|
|
is where code interpretation plays a big role. The main goal of interpretation
|
|
|
|
|
|
|
|
in phpinspect is the determination of the PHP type to display information
|
|
|
|
|
|
|
|
about. Determining this type is referred to as "resolving" in phpinspect's
|
|
|
|
|
|
|
|
codebase. Resolving a type is built around the concept of a "resolvecontext". A
|
|
|
|
|
|
|
|
resolvecontext is an object that contains information about a location in a
|
|
|
|
|
|
|
|
buffer and its surroundings. Based on the informatoin in the resolvecontext, the
|
|
|
|
|
|
|
|
type that a statement is expected to evaluate to can be determined. The code for
|
|
|
|
|
|
|
|
resolving types is mostly contained in `phpinspect-type.el`,
|
|
|
|
|
|
|
|
`phpinspect-resolve.el` and `phpinspect-resolvecontext.el`. The main entrypoints
|
|
|
|
|
|
|
|
are the functions `phpinspect-get-resolvecontext` and
|
|
|
|
|
|
|
|
`phpinspect-resolve-type-from-context`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### User-facing Functions
|
|
|
|
|
|
|
|
User-facing functions in phpinspect are mostly integrations with existing
|
|
|
|
|
|
|
|
infrastructure within emacs. For completion there is
|
|
|
|
|
|
|
|
`completion-at-point-functions`, for tooltips there is `eldoc` and for
|
|
|
|
|
|
|
|
go-to-definition there is `xref`. Aside from these integrations, phpinspect aims
|
|
|
|
|
|
|
|
to provide functionalities for code formatting.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Completion
|
|
|
|
|
|
|
|
Completion is implemented in `phpinspect-completion.el` as a strategy pattern. A
|
|
|
|
|
|
|
|
completion strategy can be added by implementing the methods
|
|
|
|
|
|
|
|
`phpinspect-comp-strategy-supports` and
|
|
|
|
|
|
|
|
`phpinspect-comp-strategy-execute`. Completion strategies are high level
|
|
|
|
|
|
|
|
abstractions that build on top of the type resolving code and the code in
|
|
|
|
|
|
|
|
`phpinspect-suggest.el`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Eldoc
|
|
|
|
|
|
|
|
Eldoc support is implemented in a strategy pattern similar to that of
|
|
|
|
|
|
|
|
completion. An eldoc strategy can be added by implementing the methods
|
|
|
|
|
|
|
|
`phpinspect-eld-strategy-supports` and `phpinspect-eld-strategy-execute`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Code Formatting
|
|
|
|
|
|
|
|
`phpinspect-fix-imports` adds, removes and sorts use statements. At the time of
|
|
|
|
|
|
|
|
writing, this is the only code formatting functionality that phpinspect
|
|
|
|
|
|
|
|
provides. See `phpinspect-imports.el` for the implementation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### Really Want To But Not Implemented: Xref
|
|
|
|
|
|
|
|
A thing that would be possible to implement on top of phpinspect's
|
|
|
|
|
|
|
|
infrastructure, but not much time has been spent on yet, is an integration with
|
|
|
|
|
|
|
|
`xref`. Xref integration would enable functionalities like go-to-definition.
|
|
|
|
|
|
|
|
|
|
|
|
### Building
|
|
|
|
### Building
|
|
|
|
|
|
|
|
|
|
|
|