It is my fervent wish that this file guide every AI coding agent working with code in this repository.
Any distilled, agent-facing documentation for this package - how it works
internally and the rationale behind key design decisions - lives in docs/.
Consult it before non-trivial changes; it is the source of truth from which the
public manual is distilled.
docs/reference.md- a catalog of every rule and type extension (interface type, what it does, config file). Consult it to find an existing extension.docs/internals.md- the four cross-cutting mechanisms that the per-extension catalog fragments: the component-tree resolver, the type-extension vocabulary,RemoveFailingReturnType, and the test harness. Read it before non-trivial edits.
nette/phpstan-rules is a PHPStan extension package for Nette library developers:
custom rules and dynamic-type extensions applied when analysing Nette libraries.
Consumed by individual Nette repos via their PHPStan configuration.
- PHP Version: 8.2 - 8.5
- Package:
nette/phpstan-rules(namespaceNette\PHPStan\)
# Static analysis (self, level 8) and tests
composer phpstan
composer tester
vendor/bin/tester tests/extensions.phpt -ssrc/(PSR-4Nette\PHPStan\); per-package extensions use dedicated sub-namespaces (Nette\PHPStan\ComponentModel\,\Schema\,\Utils\,\Application\, ...), generic PHP-level ones useNette\PHPStan\Php\.- Registration is layered NEON:
extension.neon(entry, auto-included byphpstan/extension-installer) →extension-php.neon(generic PHP) →extension-nette.neon(per-package, grouped by comment). A class is inert until tagged (phpstan.rules.rule,phpstan.broker.dynamic*ReturnTypeExtension,phpstan.ignoreErrorExtension,phpstan.broker.*ClassReflectionExtension, etc.); shared services (ComponentTreeResolver,StringsRegexHelper,TableRowTypeResolver,MapperTypeResolver) are untagged, wired by constructor injection. src/Tester/TypeAssert.phpis a reusable type-inference test helper used by other Nette packages too.
- ~25 of the ~31 files are thin adapters over a PHPStan interface - documented
one-by-one in
docs/reference.md. The real depth is the four cross-cutting things indocs/internals.md:- The component-tree model is the highest-value seam:
ComponentTreeResolver(an untagged shared service) is injected into two return-type extensions (ComponentModel and Forms) that re-parse PHP source and walk the AST with adepth = 3guard. They share dispatch logic but have a divergent fallback (Forms never returns null →BaseControl; ComponentModel returns null) - editing one dispatcher silently desyncs the pair. - Type-extension vocabulary:
getTypeFromMethodCall()returningnullmeans "decline, keep the declared type"; universal guards bail onisFirstClassCallable()and require onegetConstantStrings()for the name. - A
Rulereceives raw, non-normalized args (unlike type extensions), soValidRegularExpressionRulemust resolve the pattern by name-then-position. - The test harness needs
setAnalysedFiles()on both the parser andNodeScopeResolver; config-levelignoreErrorsruns aboveAnalyserand is not covered byTypeAssert(verified only by real projects).
- The component-tree model is the highest-value seam:
- When you add a new extension: (1) write a
.phpttest intests/with data files intests/data/and runcomposer tester; (2) add a###section todocs/reference.md; (3) add it toreadme.md.