Tools

Repository Author Commit message Committed SHA  
 
phpstan-rules by dg added Nette\Schema\Expect::array() return type narrowing
phpstan-rules by dg old Nette class names kept as aliases are reflected as the classes they stand for
phpstan-rules by dg RethrowAbortExceptionRule: don't report a dedicated catch (AbortException)

A catch that explicitly names AbortException (or a subtype) is a deliberate
swallow, e.g. Presenter::run() ending the lifecycle. Only broad catches
(\Throwable, \Exception) that swallow it incidentally are reported now.

phpstan-rules by dg added RemoveFailingReturnTypeExtension extension to remove |false from native functions and methods
phpstan-rules by dg added Registry::getMapper(), getAsset() and Mapper::getAsset() return type narrowing
phpstan-rules by dg report invalid regex patterns in Nette\Utils\Strings calls

New ValidRegularExpressionRule (the first custom rule in the project)
checks constant patterns passed to Strings::match/matchAll/split/replace
and reports compilation errors via RegexpException under identifier
nette.strings.regexpPattern. Enabled by default.

Adds TypeAssert::assertErrors() for testing rules (assertNoErrors now
delegates to it).

phpstan-rules by dg added arrow function void ignore for passing fn() => to Closure(): void parameters
phpstan-rules by dg guard dynamic return type extensions against first-class callables

Extensions called getArgs() unconditionally on the entry node. For a first-class callable such as json_encode(...) or $form[x]->m(...), PHPStan getArgs() asserts !isFirstClassCallable(), so any covered function/method/static method used in first-class-callable syntax crashed the whole analysis with an internal error. Bail out (return null) when the call node is a first-class callable, before touching getArgs(). Mirrors the standard guard in core PHPStan extensions.

phpstan-rules by dg add StringsRegexHelper for the Nette\Utils\Strings regex extensions

Shared helper that centralizes the translation of Nette's boolean
parameters into a PREG flag mask, the RegexArrayShapeMatcher calls
(matchShape/matchAllShape, instance) and stateless argument resolution
(resolveFlag/findArg, static). The Strings regex extensions added in the
following commits build on it.

phpstan-rules by dg CI: DressCode checks with the dependencies installed

Without vendor/ it knows nothing of the classes of the libraries, so a call of a deprecated method passes unnoticed.

phpstan-rules by dg support for PHP 8.6
phpstan-rules by dg added Arrays::invoke() and invokeMethod() return type narrowing
phpstan-rules by dg cs: only compiler-optimized functions in use-function imports
phpstan-rules by dg TypeAssert: opcache is switched off before analysis

A type test could see *ERROR* in place of a perfectly declared type, and
whether it did depended on which other tests happened to run beside it.
In nette/http, TypesTest failed under php-cgi on Windows only when run in
parallel with a test that touched FileUpload: getImageSize() declared as
?array{int, int} came out as *ERROR*. Alone, with -j 1, under the CLI or
with opcache disabled it passed.

The cause lies in how PHPStan finds the source of a class. For a class
outside the analysed paths, its AutoloadSourceLocator calls the autoloader
under FileReadTrapStreamWrapper, a stream wrapper that intercepts the
include, records the path and returns empty content, so the file is never
executed. Opcache defeats the trap: a script it already has in the cache
is served without the file being opened, the wrapper never sees the read
and the file really runs. Its class gets defined natively in the middle of
the analysis, and any trait or parent it needs is in turn loaded under the
trap, which fails (here "Trait Nette\SmartObject not found"), so the
reflection breaks and the type becomes *ERROR*.

Why it looked random: the CLI usually has opcache off (opcache.enable_cli),
while php-cgi has it on by the php.ini. On Windows the opcache memory is
shared by the php-cgi processes that run at the same time, so a script is
already cached in the test that analyses it exactly when a parallel test
has loaded it first. Calling opcache_compile_file() on the file before
assertTypes() reproduces it deterministically in a single process.

Opcache can be switched off at runtime (only off, never on), and once off
the scripts are compiled from the files again, so the trap works. It is
done in createContainer(), the common entry of every assertion, before
anything is analysed.

phpstan-rules by dg uses dresscode
phpstan-rules by dg suppress false argument.type on Callback::toReflection()

Its @param callable is intentionally strict, but the method escalates the
validity check to a ReflectionException at runtime, so a value that cannot
be statically proven callable (e.g. a [class-string, method] tuple) is valid
by design. New IgnoreErrorExtension matched narrowly to that single method.

phpstan-rules by dg init
phpstan-rules by dg derive exact array shape from regex in Strings::match/matchAll

StringsReturnTypeExtension now uses phpstan's RegexArrayShapeMatcher to
infer the precise shape (capture groups, named/optional groups, offsets)
for constant patterns, translating Nette's boolean parameters into a PREG
flag mask. Non-constant patterns and the lazy matchAll() Generator fall
back to the previous generic shape; split() is unchanged.

phpstan-rules by dg compatibility with PHPStan 2.2.6

RemoveFailingReturnTypeExtension: DynamicReturnTypeExtensionRegistry is
injected directly, the provider layer was removed from PHPStan.

Tests: PHPStan reverted decimal-int-string inference for regex groups
back to numeric-string.

StringsRegexHelper: matchAllShape() passes wasMatched=maybe; matchAll()
may return an empty list, and PHPStan now infers non-empty-list for yes.

phpstan-rules by dg fixed static analysis with PHPStan 2.3
phpstan-rules by dg added Container::getComponent() and $container['…'] return type narrowing

Also resolves nested access - chained ($container['a']['b']) and Nette's dash
notation ($container['a-b']) - via createComponent<Name>() factories.

phpstan-rules by dg ExpectArrayReturnTypeExtension answers the plain array element instead of Type

Since nette/schema 1.4 an Expect::array() without a shape is an ArrayType,
not a plain Type, so the extension asks Expect::array() for the class the
same way the Expect::type() one does; up to 1.3 the answer stays Type.

phpstan-rules by dg added suppression for Form event-handler callback assignments
phpstan-rules by dg added .phpt file extension
phpstan-rules by dg RemoveFailingReturnTypeExtension: preg_* narrowing only for constant patterns
phpstan-rules by dg narrow match subject string after Strings::match/matchAll

New StringsMatchTypeSpecifyingExtension narrows the subject of a
successful Strings::match()/matchAll() call inside a truthy context,
e.g. `if (Strings::match($s, '#\d+#'))` makes $s a non-empty-string.
Delegates to RegexArrayShapeMatcher::matchSubjectExpr().

phpstan-rules by dg added Html::getXxx(), setXxx(), addXxx() magic method resolution
phpstan-rules by dg added AGENTS.md & DOCS
phpstan-rules by dg RemoveFailingReturnTypeExtension: strip |false from glob()

Claude-Session: https://claude.ai/code/session_01YAusXLYHRUMofgyF5LHxqN

phpstan-rules by dg interface @property tag support (type resolution + property.notFound suppression)

PHPStan core ignores @property/@property-read tags on interfaces: the
allowsDynamicProperties() gate skips annotation-based properties there,
so e.g. $asset->url on Nette\Assets\Asset reports property.notFound and
a custom PropertiesClassReflectionExtension is never consulted.

InterfacePropertyTagTypeExtension (ExpressionTypeResolverExtension) gives
such reads the annotated type (narrowed expression types and native
properties win), InterfacePropertyTagIgnoreExtension suppresses the false
property.notFound. Writes are intentionally left unsupported.

Claude-Session: https://claude.ai/code/session_01YAusXLYHRUMofgyF5LHxqN

phpstan-rules by dg nette/database row type narrowing (table/related/ref/insert)

Narrows return types based on a configurable table-to-entity-class
mapping (nette.database.mapping.tables), which is not yet part of the
nette/database distribution. Kept as the topmost commit so the whole
feature can be excluded or rebased independently.

Includes:
- TableRowTypeResolver shared resolver
- Explorer::table() -> Selection<EntityRow>
- ActiveRow::related() -> GroupedSelection<EntityRow>
- ActiveRow::ref() -> ?EntityRow
- Selection::insert() -> mapped EntityRow

phpstan-rules by dg cs

Last synchronization: 2026-11-10 08:03:12