# PHPStan Error Identifier Documentation

This directory contains markdown documentation files for PHPStan error identifiers. Each file explains what a specific error means, shows a triggering code example, and offers ways to fix it.

## How these files are generated

Files are generated by a GitHub Actions workflow (`.github/workflows/generate-error-docs.md`) that uses Claude to:

1. Read `website/src/errorsIdentifiers.json` which maps each identifier to its rule classes and source code locations
2. Pick undocumented identifiers (ones without a corresponding `.md` file here)
3. Clone referenced PHPStan repositories (`phpstan-src`, `phpstan-strict-rules`, `phpstan-doctrine`, etc.)
4. Research each identifier by reading rule source code and test fixtures
5. Generate a markdown file for each identifier

## File format

Each file follows this exact structure:

```markdown
---
title: "<identifier>"
shortDescription: "One sentence describing when this error is reported."
ignorable: true
---

## Code example

` ``php
<?php declare(strict_types = 1);

// Minimal PHP code that triggers this error
` ``

## Why is it reported?

Explanation from PHP language perspective.

## How to fix it

Ways to fix the error.
```

### Frontmatter

- `title`: The error identifier (e.g., `"property.notFound"`)
- `shortDescription`: One sentence (ending with a period) describing when the error is reported. Written from a user's perspective — what code pattern causes PHPStan to report this error. Examples: `"Accessing a private property from outside the declaring class."`, `"Loose comparison using == will always evaluate to true."`, `"Pure function uses print, which produces output as a side effect."`
- `ignorable`: `true` for most identifiers. Set to `false` for identifiers that use `->nonIgnorable()` in the rule builder chain, or that start with `phpstan.` or `phpstanPlayground.`

### Code example

- Must be valid PHP that triggers the identifier
- Starts with `<?php declare(strict_types = 1);`
- Uses `php` language tag
- Keep it minimal — remove unrelated classes, simplify names
- Prefer real code from test fixtures when possible

### "Why is it reported?" section

- Explain PHP language semantics, not PHPStan internals
- PHPStan points to code that causes crashes, doesn't execute at all, or doesn't do what the developer probably intended
- Be concise and technically precise
- List multiple reasons if applicable
- If the rule's `->tip()` links to a blog post on phpstan.org, mention it: `Learn more: [Blog post title](/blog/post-slug)`

### "How to fix it" section

- Offer multiple ways to fix when applicable
- Use `diff-php` syntax for code changes
- Prefer fixes in this order:
  1. Fix the actual bug
  2. Narrow the type using native PHP type declarations
  3. Narrow the type using PHPDoc types (`@param`, `@return`, `@var` on properties)
  4. Use [type narrowing](/writing-php-code/narrowing-types) in the function body
  5. Configure PHPStan if the rule is configurable
- When the error involves a PHP language feature only available in newer PHP versions, mention the PHPDoc-based alternative that works on older versions too. For example: native return type `never` (PHP 8.1+) can be replaced with `@return never`, native union types (PHP 8.0+) can be expressed as PHPDoc union types, native intersection types (PHP 8.1+) can be expressed as PHPDoc intersection types, standalone types like `true`/`false`/`null` (PHP 8.2+) can be written in PHPDoc. Link to [PHPDoc Basics](/writing-php-code/phpdocs-basics) and [PHPDoc Types](/writing-php-code/phpdoc-types) where relevant.
- Every time a configuration parameter is mentioned, link it to the correct documentation page. Consult `website/src/config-reference.md` to find the right anchor — parameters that have their own `###` heading (like `phpVersion`) link to `/config-reference#phpversion`. Parameters that only appear as "Related config keys" link to the user guide page referenced there (e.g., `reportUnmatchedIgnoredErrors` links to `/user-guide/ignoring-errors#reporting-unused-ignores`, `scanFiles` links to `/user-guide/discovering-symbols#third-party-code-outside-of-composer-dependencies`).
- Show code fixes. Use `diff-php` syntax when showing changes:

````markdown
```diff-php
- $value = $this->getValue();
+ $value = (string) $this->getValue();
```
````

### Do NOT

- Suggest using `assert()` for type narrowing
- Suggest throwing an exception to narrow types
- Suggest using inline `@var` PHPDoc tag
- Suggest ignoring the error (the detail page already covers that)
- Use emojis or first person

## Identifier prefix reference

Some prefixes are non-obvious because they come from `ClassNameUsageLocation`:

| Prefix | PHP Feature |
|--------|-------------|
| `assert` | `@phpstan-assert` PHPDoc tag (NOT `assert()` function) |
| `attribute` | PHP 8.0+ attributes `#[AttributeName]` |
| `catch` | `catch (ExceptionClass $e)` blocks |
| `classConstant` | `ClassName::CONSTANT` access |
| `instanceof` | `$x instanceof ClassName` expressions |
| `methodTag` | `@method` PHPDoc tag |
| `mixin` | `@mixin` PHPDoc tag |
| `new` | `new ClassName()` instantiation |
| `parameter` | Native type declaration on function/method parameter |
| `property` | Native type declaration on class property (e.g., `private Foo $bar`) |
| `propertyTag` | `@property` PHPDoc tag |
| `requireExtends` | `@phpstan-require-extends` PHPDoc tag |
| `requireImplements` | `@phpstan-require-implements` PHPDoc tag |
| `return` | Native return type declaration |
| `sealed` | `@phpstan-sealed` PHPDoc tag |
| `selfOut` | `@phpstan-self-out` PHPDoc tag |
| `staticMethod` | `ClassName::method()` static method calls |
| `staticProperty` | `ClassName::$property` static property access |
| `traitUse` | `use TraitName` in a class body |
| `typeAlias` | PHPStan type alias references |
| `varTag` | `@var` PHPDoc tag |

### Prefixes with special identifier format

| Prefix pattern | PHP Feature |
|----------------|-------------|
| `class.extends*` | `class Foo extends ParentClass` |
| `class.implements*` | `class Foo implements Interface` |
| `enum.implements*` | `enum Foo implements Interface` |
| `interface.extends*` | `interface Foo extends OtherInterface` |
| `generics.*Bound` | `@template T of BoundClass` bound constraint |
| `generics.*Default` | `@template T = DefaultClass` default value |

## Tone and style

- Concise, technically precise, no filler words
- Match existing phpstan.org documentation style
- Direct and practical
- For extension-specific identifiers (phpstan-doctrine, phpstan-symfony, etc.), mention which extension package provides the rule

## Example

For `website/errors/deadCode.unreachable.md`:

```markdown
---
title: "deadCode.unreachable"
shortDescription: "Code after a return, throw, or other terminating statement can never be executed."
ignorable: true
---

## Code example

` ``php
<?php declare(strict_types = 1);

function doFoo(): int
{
	return 1;
	echo 'unreachable';
}
` ``

## Why is it reported?

The statement after `return` can never be executed. The `return` statement unconditionally transfers control out of the function, making any code following it in the same block dead code. This usually indicates a logic error or leftover code from refactoring.

## How to fix it

Remove the unreachable code:

` ``diff-php
 function doFoo(): int
 {
 	return 1;
-	echo 'unreachable';
 }
` ``

If the code should execute, restructure the logic so it runs before the return:

` ``diff-php
 function doFoo(): int
 {
+	echo 'this should run';
 	return 1;
-	echo 'unreachable';
 }
` ``
```
