diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 42781bfb..b92430fd 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -16,10 +16,14 @@ jobs:
with:
go-version: 'stable'
- - run: gofmt -l . | tee /tmp/gofmt.out && test ! -s /tmp/gofmt.out
+ - run: test -z "$(gofmt -l .)" || { gofmt -d .; exit 1; }
+ name: gofmt
- run: go vet ./...
- run: go build ./...
- - run: go test ./...
+ - run: go test -race ./...
+
+ - run: go mod tidy && git diff --exit-code go.mod go.sum
+ name: go mod tidy is up to date
diff --git a/README.md b/README.md
index d48c3a50..e8dd38cd 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,54 @@
-# Fill Parameter Types based on Passed Values
+# ArgTyper
-There are often more known types in your project than meets the eye.
-This tool detects the types of **literal values** passed into method, constructor and function calls, then adds them as parameter type declarations.
+Add missing PHP parameter types based on the values you already pass in.
+
+Your code often carries more type information than the signatures show. Every
+time you call a method with a literal value, that value has a type. ArgTyper
+reads those calls and writes the type back onto the parameter.
+
+It is a single Go binary built on
+[php-parser-in-go](https://github.com/rectorphp/php-parser-in-go) - no PHP,
+Composer or PHPStan needed to run it.
+## Example
+
+Say you have a class with an untyped parameter, and a caller that passes an
+`int` into it:
+
```php
+// src/HotelOverview.php
final class HotelOverview
{
public function makeRoomAvailable($roomNumber)
{
}
- public function bookLobby()
+ public function openLobby()
{
$this->makeRoomAvailable(324);
}
}
```
-✅ An `int` value is passed into `makeRoomAvailable()`.
+Run the tool:
-
+```bash
+argtyper add-types .
+```
+
+```
+Code dirs found in ".": [src]
+
+1. Collecting argument types...
+ Found 1 arg types
-The tool fills in the missing type declaration:
+2. Adding types to parameters...
+ Finished! Added 1 new types
+```
+
+`324` is an `int`, so an `int` type is added to `$roomNumber`:
```diff
final class HotelOverview
@@ -35,67 +60,103 @@ The tool fills in the missing type declaration:
}
```
-✅ An `int` parameter type is added to the `makeRoomAvailable()` method.
+That's it.
-That's it.
+## More of what it does
-
+**Functions and constructors**, not just methods:
-## Install
+```diff
+-function greet($name)
++function greet(string $name)
+ {
+ }
-```bash
-go install github.com/rectorphp/argtyper@latest
+ greet("Tomas");
```
-
+**Nullable** when a value and `null` are both passed:
-## Usage
+```php
+$this->setLocale("en");
+$this->setLocale(null);
+```
+
+```diff
+-public function setLocale($locale)
++public function setLocale(?string $locale)
+ {
+ }
+```
+
+**Skips the ambiguous cases** - if two different types reach the same
+parameter, it leaves the parameter alone rather than guessing:
+
+```php
+$this->handle(1); // int
+$this->handle("text"); // string -> parameter is left untyped
+```
+
+
-Run it in your project directory:
+## Install
```bash
-argtyper add-types .
+go install github.com/rectorphp/argtyper@latest
```
-Or on another project:
+## Usage
```bash
-argtyper add-types /path/to/project
+argtyper add-types [project-path]
```
-It scans the `src`, `lib`, `app`, `test` and `tests` directories.
+`project-path` defaults to the current directory. The tool scans the `src`,
+`lib`, `app`, `test` and `tests` directories.
-## How It Works
-
-It is built on [php-parser-in-go](https://github.com/rectorphp/php-parser-in-go).
+## How it works
-1. It walks every call site and records the type of each **literal** argument - `int`, `float`, `string`, `bool`, `array`, `null` and `new X()` (as `object`).
-2. It groups the recorded types per parameter position.
-3. It adds the type to each definition that is still missing one.
+1. **Collect** - it walks every call site and records the type of each *literal*
+ argument: `int`, `float`, `string`, `bool`, `array`, `null`, and `new X()`
+ as an object.
+2. **Resolve** - it groups the recorded types per parameter position and keeps
+ only the unambiguous ones: a single type, or a single type plus `null`
+ (which becomes nullable).
+3. **Apply** - it adds each resolved type to the definitions that are still
+ missing one, and reprints the file. Unchanged code keeps its exact
+ formatting.
-With a few exceptions:
+Rules it follows:
-* If multiple different types are found for one parameter -> it is skipped as ambiguous.
-* If a single type plus `null` is found -> a nullable type is added.
* Parameters that already have a type are left untouched.
-* Magic methods (except `__construct`) are skipped.
-* Methods that may override a parent or interface are skipped, unless they are private or a constructor.
+* Multiple different types for one parameter are skipped as ambiguous.
+* Magic methods are skipped, except `__construct`.
+* Methods that might override a parent or interface are skipped, unless they
+ are private or a constructor.
## Scope
-The tool relies only on the parsed syntax tree, not on full type inference, so it works with values it can resolve statically:
-
-* **Literal arguments** - `f(324)`, `f("x")`, `f([1, 2])`. Variables and expressions are skipped.
-* **Statically resolvable call targets** - `new X()`, `X::method()`, `self::method()`, `$this->method()` and plain `function()` calls. Calls on other variables (`$service->method()`) are skipped, because the class cannot be known without type inference.
-* **Short class names** - classes are matched by their short name, not the fully qualified name.
-
-This catches the easy, unambiguous cases and leaves the rest for you to fill manually based on PHPStan or test feedback.
+ArgTyper reads the syntax tree only - it does not run full type inference. That
+keeps it fast and dependency-free, and limits it to what can be resolved
+statically:
+
+* **Literal arguments** - `f(324)`, `f("x")`, `f([1, 2])`. Variables and
+ expressions are skipped, because their type is unknown without inference.
+* **Statically resolvable targets** - `new X()`, `X::method()`, `self::method()`,
+ `$this->method()` and plain `function()` calls. A call on another variable,
+ such as `$service->method(...)`, is skipped, because the class behind
+ `$service` cannot be known from syntax alone.
+* **Short class names** - classes are matched by their short name, not the fully
+ qualified name.
+
+It fills in the easy, safe cases and leaves the rest for you to complete based
+on PHPStan or test feedback.