|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +`nette/phpstan-rules` is a PHPStan extension package for Nette library developers. It provides custom rules and type extensions used when analysing Nette libraries with PHPStan. The package is consumed by individual Nette repositories via their PHPStan configuration. |
| 8 | + |
| 9 | +## Commands |
| 10 | + |
| 11 | +```bash |
| 12 | +composer phpstan # Run static analysis (level 8) |
| 13 | +composer tester # Run all tests |
| 14 | +vendor/bin/tester tests/SomeTest.phpt -s # Run a single test |
| 15 | +``` |
| 16 | + |
| 17 | +## Architecture |
| 18 | + |
| 19 | +- **`src/`** — Extension source code, PSR-4 autoloaded under `Nette\PHPStan\` namespace |
| 20 | +- **`src/Tester/TypeAssert.php`** — Reusable type inference testing helper for Nette Tester (used by other Nette packages) |
| 21 | +- **`extension.neon`** — Entry point, includes per-extension configs (`extension-*.neon`), auto-included by `phpstan/extension-installer` |
| 22 | +- **`extension-*.neon`** — Self-contained config for each extension (parameters + services) |
| 23 | +- **`phpstan.neon`** — Self-analysis config (level 8, analyses `src/` and `tests/`) |
| 24 | + |
| 25 | +### How extensions are registered |
| 26 | + |
| 27 | +Each extension class is registered as a service in NEON with the appropriate tag. Common tags: |
| 28 | +- `phpstan.rules.rule` — custom rules |
| 29 | +- `phpstan.collector` — collectors |
| 30 | +- `phpstan.broker.expressionTypeResolverExtension` — expression type resolution (runs before all dynamic extensions) |
| 31 | +- `phpstan.broker.dynamicFunctionReturnTypeExtension` — dynamic function return types |
| 32 | +- `phpstan.broker.dynamicMethodReturnTypeExtension` — dynamic instance method return types |
| 33 | +- `phpstan.broker.dynamicStaticMethodReturnTypeExtension` — dynamic static method return types |
| 34 | +- `phpstan.ignoreErrorExtension` — conditional error suppression |
| 35 | +- `phpstan.broker.propertiesClassReflectionExtension` — magic properties |
| 36 | +- `phpstan.broker.methodsClassReflectionExtension` — magic methods |
| 37 | +- `phpstan.broker.typeSpecifyingExtension` — type narrowing |
| 38 | + |
| 39 | +### RemoveFalseReturnTypeExtension |
| 40 | + |
| 41 | +`RemoveFalseReturnTypeExtension` (`ExpressionTypeResolverExtension`) removes `|false` from return types of native PHP functions and methods where false is trivial or outdated. It handles `FuncCall`, `MethodCall`, and `StaticCall` in a single class. Configuration uses a flat list in NEON — plain names for functions (`json_encode`), `Class::method` notation for methods (`Normalizer::normalize`). It runs before all `DynamicReturnTypeExtension` implementations, delegates to them via `DynamicReturnTypeExtensionRegistry`, and strips `|false` from the result. Config: `extension-php.neon`. |
| 42 | + |
| 43 | +### Testing |
| 44 | + |
| 45 | +Tests use **Nette Tester** (not PHPUnit). Test files are `.phpt` in `tests/` with data files in `tests/data/`. |
| 46 | + |
| 47 | +Type inference tests use `Nette\PHPStan\Tester\TypeAssert::assertTypes()` which creates a PHPStan DI container, walks AST via `NodeScopeResolver`, and verifies `assertType()` calls from test data files. Important: both `PathRoutingParser` and `NodeScopeResolver` need `setAnalysedFiles()` — without the parser call, function bodies get stripped by `CleaningParser`. This class is designed to be reusable by other Nette packages. |
| 48 | + |
| 49 | +## Related Repositories |
| 50 | + |
| 51 | +- **PHPStan source code** — `W:\libs.3rd\phpstan` |
| 52 | + |
| 53 | +## `doc/` Directory Reference |
| 54 | + |
| 55 | +Read the relevant documentation files **before** writing code on PHPStan extensions. |
| 56 | + |
| 57 | +### Foundation (read as prerequisites) |
| 58 | + |
| 59 | +| File | Read when... | |
| 60 | +|------|-------------| |
| 61 | +| `doc/core-concepts.md` | First contact with PHPStan extension development; unsure where to start | |
| 62 | +| `doc/abstract-syntax-tree.md` | Writing a custom rule — need to pick the right AST node for `getNodeType()` | |
| 63 | +| `doc/scope.md` | Getting expression types via `$scope->getType()`, determining context (class, method, namespace) | |
| 64 | +| `doc/type-system.md` | Creating, comparing, or combining types; using `isSuperTypeOf()` or `TypeCombinator` | |
| 65 | +| `doc/trinary-logic.md` | Working with `isSuperTypeOf()` results (returns TrinaryLogic, not bool) | |
| 66 | +| `doc/reflection.md` | Need introspection of classes/methods/properties; reading PHPDoc via `FileTypeMapper` | |
| 67 | + |
| 68 | +### Infrastructure |
| 69 | + |
| 70 | +| File | Read when... | |
| 71 | +|------|-------------| |
| 72 | +| `doc/dependency-injection-configuration.md` | Registering any extension in NEON config (services, tags, autowiring) | |
| 73 | +| `doc/testing.md` | Writing tests for any extension — `RuleTestCase`, `TypeInferenceTestCase`, `assertType()` | |
| 74 | +| `doc/extension-types.md` | Don't know which extension type to use — navigation hub for all types | |
| 75 | +| `doc/backward-compatibility-promise.md` | Extending or implementing PHPStan classes/interfaces; checking `@api` tags | |
| 76 | +| `doc/extension-library.md` | Looking for existing extensions for a framework/library | |
| 77 | + |
| 78 | +### Extension types — specific triggers |
| 79 | + |
| 80 | +| File | Read when... | |
| 81 | +|------|-------------| |
| 82 | +| `doc/rules.md` | Writing a custom rule (`Rule` interface), using `RuleErrorBuilder`, working with virtual nodes | |
| 83 | +| `doc/collectors.md` | Rule needs data from the entire codebase (unused code detection, cross-file analysis) | |
| 84 | +| `doc/restricted-usage-extensions.md` | Forbidding method/function/class/property/constant usage from certain contexts (simpler than full rules) | |
| 85 | +| `doc/class-reflection-extensions.md` | Class uses magic `__get`/`__set`/`__call` — need to teach PHPStan about dynamic properties/methods | |
| 86 | +| `doc/dynamic-return-type-extensions.md` | Return type depends on arguments and generics/conditional PHPDoc are not sufficient | |
| 87 | +| `doc/dynamic-throw-type-extensions.md` | Thrown exception depends on arguments | |
| 88 | +| `doc/type-specifying-extensions.md` | Custom assertion/`is_*()` function and PHPStan doesn't recognize type narrowing; `@phpstan-assert` not sufficient | |
| 89 | +| `doc/closure-extensions.md` | Closure parameter types or `$this` depend on surrounding context | |
| 90 | +| `doc/custom-phpdoc-types.md` | Creating a custom PHPDoc utility type (`TypeNodeResolverExtension`) | |
| 91 | +| `doc/allowed-subtypes.md` | Implementing sealed classes — restricting which classes can extend a given class/interface | |
| 92 | +| `doc/always-read-written-properties.md` | PHPStan reports property as unused but it's accessed via reflection/magic | |
| 93 | +| `doc/always-used-class-constants.md` | PHPStan reports constant as unused but it's accessed via reflection | |
| 94 | +| `doc/always-used-methods.md` | PHPStan reports method as unused but it's called via reflection/magic | |
| 95 | +| `doc/custom-deprecations.md` | Using custom deprecation attributes (not standard `@deprecated`) | |
| 96 | +| `doc/error-formatters.md` | Creating a custom output format for PHPStan errors | |
| 97 | +| `doc/ignore-error-extensions.md` | Conditionally ignoring errors based on context (scope, node, error type) | |
| 98 | +| `doc/result-cache-meta-extensions.md` | Extension depends on external data and needs custom cache invalidation | |
0 commit comments