Skip to content

Commit 9007feb

Browse files
committed
Add "Extract interface" code action to generate interfaces from classes
1 parent cc26c0b commit 9007feb

8 files changed

Lines changed: 894 additions & 74 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- **Convert to arrow function.** A new `refactor.rewrite` code action converts single-expression closures to arrow functions (`function($x) { return $x * 2; }` to `fn($x) => $x * 2`). The action is only offered when the conversion is safe: single return statement, no by-reference `use` captures, no `void`/`never` return type, and PHP >= 7.4.
13+
- **Extract interface.** A new `refactor.extract` code action generates an interface from a concrete class. All public method signatures (excluding the constructor) are extracted into a new `{ClassName}Interface.php` file in the same directory, and the class is updated with `implements {ClassName}Interface`. Class-level and method-level `@template` tags are preserved when referenced by extracted methods.
1314
- **`@template` on `@method` tags.** Virtual methods declared via `@method` PHPDoc tags can now define their own template parameters using the `<T of Bound>` syntax (e.g. `@method TVal get<TVal of mixed>(TVal $default)`). Template inference at call sites works the same as for real methods.
1415
- **Laravel custom Eloquent builder support.** Models using the `#[UseEloquentBuilder]` attribute now have their custom builder's methods forwarded as static methods on the model. `query()`, `newQuery()`, and `newModelQuery()` return the custom builder type with correct generic model substitution. Contributed by @MingJen in https://github.com/AJenbo/phpantom_lsp/pull/118.
1516

docs/todo.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ within the same impact tier.
2727
| --- | --------------------------------------------------------------------------------------------------------------------- | ---------- | ------ |
2828
| C2 | [`#[ArrayShape]` return shapes on stub functions](todo/completion.md#c2-arrayshape-return-shapes-on-stub-functions) | Medium | Medium |
2929
| A3 | Switch → match conversion | Low-Medium | Medium |
30-
| A10 | [Generate interface from class](todo/actions.md#a10-generate-interface-from-class) | Low-Medium | Medium |
3130
| D10 | [PHPMD diagnostic proxy](todo/diagnostics.md#d10-phpmd-diagnostic-proxy) | Low | Medium |
3231

3332
## Sprint 7 — 1.0 release & IDE extensions

docs/todo/actions.md

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -95,78 +95,6 @@ but bounded in scope.
9595

9696

9797

98-
---
99-
100-
## A10. Generate Interface from Class
101-
102-
**Impact: Low-Medium · Effort: Medium**
103-
104-
Extract an interface from an existing class. The new interface contains
105-
method signatures for all public methods in the class, and the class is
106-
updated to implement it.
107-
108-
### Behaviour
109-
110-
- **Trigger:** Cursor is on a class declaration. The code action
111-
"Extract interface" appears.
112-
- **Code action kind:** `refactor.extract`.
113-
- **Result:** A new file is created containing the interface, and the
114-
original class is updated to add `implements InterfaceName`.
115-
116-
### What gets extracted
117-
118-
- All `public` methods (excluding the constructor) become interface
119-
method signatures: visibility, name, parameters with types and
120-
defaults, and return type.
121-
- PHPDoc blocks from the extracted methods are copied to the interface
122-
(they often contain `@param`, `@return`, and `@template` tags that
123-
are essential for type information).
124-
- Class-level `@template` tags are copied if any extracted method
125-
references those template parameters.
126-
- Public constants are **not** extracted (interface constants have
127-
different semantics and this is rarely what users want).
128-
129-
### Naming
130-
131-
Default interface name: `{ClassName}Interface`. Place it in the same
132-
namespace and directory as the class. If the file uses PSR-4, the
133-
interface file path is derived from the namespace.
134-
135-
### Implementation
136-
137-
- Parse the class to collect public method signatures and their
138-
docblocks.
139-
- Collect class-level `@template` tags if referenced by extracted
140-
methods.
141-
- Generate the interface source: namespace declaration, use imports
142-
needed by the method signatures, interface declaration with method
143-
stubs.
144-
- Build a `WorkspaceEdit` with two operations:
145-
1. `CreateFile` + `TextEdit` for the new interface file.
146-
2. `TextEdit` on the original class to add `implements InterfaceName`
147-
(and a `use` import if the interface is in a different file, though
148-
by default it's the same namespace).
149-
- Format the generated interface to match the project's indentation
150-
style (detect from the source class).
151-
152-
### Edge cases
153-
154-
- **Class already implements interfaces:** Append to the existing
155-
`implements` list rather than replacing it.
156-
- **Abstract methods:** Include them in the interface (they're already
157-
stubs).
158-
- **Static methods:** Include them. Interfaces can declare static method
159-
signatures.
160-
- **Generic classes:** If the class has `@template T` and a method
161-
returns `T`, the interface needs the same `@template` tag.
162-
163-
### Prerequisites
164-
165-
| Feature | What it contributes |
166-
| ----------------------------------- | --------------------------------------------------------------------------------- |
167-
| Parser (`parser/classes.rs`) | Extracts public method signatures with full type information |
168-
| Implement missing methods (shipped) | Shared infrastructure for generating method stubs and `implements` clause editing |
169-
17098
---
17199

172100
## A16. Snippet Placeholder for Extracted Method Name

0 commit comments

Comments
 (0)