├── .editorconfig
├── LICENSE
├── README.md
├── composer.json
├── extension.neon
├── rules.neon
├── src
├── Reflection
│ └── Nette
│ │ ├── HtmlClassReflectionExtension.php
│ │ ├── HtmlMethodReflection.php
│ │ ├── HtmlPropertyReflection.php
│ │ ├── NetteObjectClassReflectionExtension.php
│ │ ├── NetteObjectEventListenerMethodReflection.php
│ │ └── NetteObjectPropertyReflection.php
├── Rule
│ └── Nette
│ │ ├── DoNotExtendNetteObjectRule.php
│ │ ├── PresenterInjectedPropertiesExtension.php
│ │ ├── RegularExpressionPatternRule.php
│ │ └── RethrowExceptionRule.php
└── Type
│ └── Nette
│ ├── ComponentGetPresenterDynamicReturnTypeExtension.php
│ ├── ComponentLookupDynamicReturnTypeExtension.php
│ ├── ComponentModelArrayAccessDynamicReturnTypeExtension.php
│ ├── ComponentModelDynamicReturnTypeExtension.php
│ ├── FormContainerUnsafeValuesDynamicReturnTypeExtension.php
│ ├── FormContainerValuesDynamicReturnTypeExtension.php
│ ├── FormsBaseControlDynamicReturnTypeExtension.php
│ ├── PresenterGetSessionReturnTypeExtension.php
│ ├── ServiceLocatorDynamicReturnTypeExtension.php
│ ├── StringsMatchAllDynamicReturnTypeExtension.php
│ ├── StringsMatchDynamicReturnTypeExtension.php
│ └── StringsReplaceCallbackClosureTypeExtension.php
└── stubs
├── Application
├── Routers
│ └── RouteList.stub
└── UI
│ ├── Component.stub
│ ├── Multiplier.stub
│ └── Presenter.stub
├── Caching
└── Cache.stub
├── ComponentModel
├── Component.stub
├── Container.stub
├── IComponent.stub
└── IContainer.stub
├── Database
├── ResultSet.stub
└── Table
│ ├── ActiveRow.stub
│ └── Selection.stub
├── Forms
├── Container.stub
├── Form.stub
└── Rules.stub
├── Http
├── FileUpload.stub
└── SessionSection.stub
├── Routing
└── Router.stub
└── Utils
├── ArrayHash.stub
├── Arrays.stub
├── Callback.stub
├── Helpers.stub
├── Html.stub
├── Paginator.stub
└── Random.stub
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 |
9 | [*.{php,phpt}]
10 | indent_style = tab
11 | indent_size = 4
12 |
13 | [*.xml]
14 | indent_style = tab
15 | indent_size = 4
16 |
17 | [*.neon]
18 | indent_style = tab
19 | indent_size = 4
20 |
21 | [*.{yaml,yml}]
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [composer.json]
26 | indent_style = tab
27 | indent_size = 4
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Ondřej Mirtes
4 | Copyright (c) 2025 PHPStan s.r.o.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nette Framework extension for PHPStan
2 |
3 | [](https://github.com/phpstan/phpstan-nette/actions)
4 | [](https://packagist.org/packages/phpstan/phpstan-nette)
5 | [](https://packagist.org/packages/phpstan/phpstan-nette)
6 |
7 | * [PHPStan](https://phpstan.org/)
8 | * [Nette Framework](https://nette.org/)
9 |
10 | This extension provides following features:
11 |
12 | * `Nette\ComponentModel\Container::getComponent()` knows type of the component because it reads the return type on `createComponent*` (this works best in presenters and controls)
13 | * `Nette\DI\Container::getByType` and `createInstance` return type based on first parameter (`Foo::class`).
14 | * `Nette\Forms\Container::getValues` return type based on `$asArray` parameter.
15 | * `Nette\ComponentModel\Component::lookup` return type based on `$throw` parameter.
16 | * `Nette\Application\UI\Component::getPresenter` return type based on `$throw` parameter.
17 | * Dynamic methods of [Nette\Utils\Html](https://doc.nette.org/en/2.4/html-elements)
18 | * Magic [Nette\Object and Nette\SmartObject](https://doc.nette.org/en/2.4/php-language-enhancements) properties
19 | * Event listeners through the `on*` properties
20 | * Defines early terminating method calls for Presenter methods to prevent `Undefined variable` errors
21 | * Understand the exact array shape coming from `Nette\Utils\Strings::match()` and `Nette\Utils\Strings::matchAll()` based on pattern
22 |
23 | It also contains these framework-specific rules (can be enabled separately):
24 |
25 | * Do not extend Nette\Object, use Nette\SmartObject trait instead
26 | * Rethrow exceptions that are always meant to be rethrown (like `AbortException`)
27 |
28 |
29 | ## Installation
30 |
31 | To use this extension, require it in [Composer](https://getcomposer.org/):
32 |
33 | ```
34 | composer require --dev phpstan/phpstan-nette
35 | ```
36 |
37 | If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set!
38 |
39 |
40 | Manual installation
41 |
42 | If you don't want to use `phpstan/extension-installer`, include extension.neon in your project's PHPStan config:
43 |
44 | ```
45 | includes:
46 | - vendor/phpstan/phpstan-nette/extension.neon
47 | ```
48 |
49 | To perform framework-specific checks, include also this file:
50 |
51 | ```
52 | - vendor/phpstan/phpstan-nette/rules.neon
53 | ```
54 |
55 |
56 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phpstan/phpstan-nette",
3 | "type": "phpstan-extension",
4 | "description": "Nette Framework class reflection extension for PHPStan",
5 | "license": [
6 | "MIT"
7 | ],
8 | "require": {
9 | "php": "^7.4 || ^8.0",
10 | "phpstan/phpstan": "^2.1.12"
11 | },
12 | "conflict": {
13 | "nette/application": "<2.3.0",
14 | "nette/component-model": "<2.3.0",
15 | "nette/di": "<2.3.0",
16 | "nette/forms": "<2.3.0",
17 | "nette/http": "<2.3.0",
18 | "nette/utils": "<2.3.0"
19 | },
20 | "require-dev": {
21 | "nette/application": "^3.0",
22 | "nette/di": "^3.1.10",
23 | "nette/forms": "^3.0",
24 | "nette/utils": "^2.3.0 || ^3.0.0",
25 | "php-parallel-lint/php-parallel-lint": "^1.2",
26 | "phpstan/phpstan-deprecation-rules": "^2.0",
27 | "phpstan/phpstan-phpunit": "^2.0",
28 | "phpstan/phpstan-strict-rules": "^2.0",
29 | "phpunit/phpunit": "^9.6"
30 | },
31 | "config": {
32 | "platform": {
33 | "php": "7.4.6"
34 | },
35 | "sort-packages": true
36 | },
37 | "extra": {
38 | "phpstan": {
39 | "includes": [
40 | "extension.neon",
41 | "rules.neon"
42 | ]
43 | }
44 | },
45 | "autoload": {
46 | "psr-4": {
47 | "PHPStan\\": "src/"
48 | }
49 | },
50 | "autoload-dev": {
51 | "classmap": [
52 | "tests/"
53 | ]
54 | },
55 | "minimum-stability": "dev",
56 | "prefer-stable": true
57 | }
58 |
--------------------------------------------------------------------------------
/extension.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | additionalConstructors:
3 | - Nette\Application\UI\Presenter::startup
4 | exceptions:
5 | uncheckedExceptionClasses:
6 | - 'Nette\InvalidArgumentException'
7 | stubFiles:
8 | - stubs/Application/Routers/RouteList.stub
9 | - stubs/Application/UI/Component.stub
10 | - stubs/Application/UI/Multiplier.stub
11 | - stubs/Application/UI/Presenter.stub
12 | - stubs/Caching/Cache.stub
13 | - stubs/ComponentModel/Component.stub
14 | - stubs/ComponentModel/Container.stub
15 | - stubs/ComponentModel/IComponent.stub
16 | - stubs/ComponentModel/IContainer.stub
17 | - stubs/Database/ResultSet.stub
18 | - stubs/Database/Table/ActiveRow.stub
19 | - stubs/Database/Table/Selection.stub
20 | - stubs/Forms/Container.stub
21 | - stubs/Forms/Form.stub
22 | - stubs/Forms/Rules.stub
23 | - stubs/Http/SessionSection.stub
24 | - stubs/Http/FileUpload.stub
25 | - stubs/Routing/Router.stub
26 | - stubs/Utils/ArrayHash.stub
27 | - stubs/Utils/Arrays.stub
28 | - stubs/Utils/Callback.stub
29 | - stubs/Utils/Helpers.stub
30 | - stubs/Utils/Html.stub
31 | - stubs/Utils/Paginator.stub
32 | - stubs/Utils/Random.stub
33 | universalObjectCratesClasses:
34 | - Nette\Application\UI\ITemplate
35 | - Nette\Application\UI\Template
36 | - Nette\Bridges\ApplicationLatte\Template
37 | - Nette\Database\IRow
38 | - Nette\Http\SessionSection
39 | - Nette\Security\Identity
40 | - Nette\Security\SimpleIdentity
41 | earlyTerminatingMethodCalls:
42 | Nette\Application\UI\Component:
43 | - error
44 | Nette\Application\UI\Presenter:
45 | - redirect
46 | - redirectUrl
47 | - sendJson
48 | - sendPayload
49 | - sendResponse
50 | - sendTemplate
51 | - terminate
52 | - forward
53 |
54 | services:
55 | -
56 | class: PHPStan\Reflection\Nette\HtmlClassReflectionExtension
57 | tags:
58 | - phpstan.broker.propertiesClassReflectionExtension
59 | - phpstan.broker.methodsClassReflectionExtension
60 |
61 | -
62 | class: PHPStan\Reflection\Nette\NetteObjectClassReflectionExtension
63 | tags:
64 | - phpstan.broker.propertiesClassReflectionExtension
65 | - phpstan.broker.methodsClassReflectionExtension
66 |
67 | -
68 | class: PHPStan\Type\Nette\ComponentModelArrayAccessDynamicReturnTypeExtension
69 | tags:
70 | - phpstan.broker.dynamicMethodReturnTypeExtension
71 |
72 | -
73 | class: PHPStan\Type\Nette\ComponentModelDynamicReturnTypeExtension
74 | tags:
75 | - phpstan.broker.dynamicMethodReturnTypeExtension
76 |
77 | -
78 | class: PHPStan\Type\Nette\ComponentLookupDynamicReturnTypeExtension
79 | tags:
80 | - phpstan.broker.dynamicMethodReturnTypeExtension
81 |
82 | -
83 | class: PHPStan\Type\Nette\ComponentGetPresenterDynamicReturnTypeExtension
84 | tags:
85 | - phpstan.broker.dynamicMethodReturnTypeExtension
86 |
87 | -
88 | class: PHPStan\Type\Nette\FormsBaseControlDynamicReturnTypeExtension
89 | tags:
90 | - phpstan.broker.dynamicMethodReturnTypeExtension
91 |
92 | -
93 | class: PHPStan\Type\Nette\PresenterGetSessionReturnTypeExtension
94 | tags:
95 | - phpstan.broker.dynamicMethodReturnTypeExtension
96 |
97 | -
98 | class: PHPStan\Type\Nette\ServiceLocatorDynamicReturnTypeExtension
99 | tags:
100 | - phpstan.broker.dynamicMethodReturnTypeExtension
101 |
102 | -
103 | class: PHPStan\Type\Nette\FormContainerUnsafeValuesDynamicReturnTypeExtension
104 | tags:
105 | - phpstan.broker.dynamicMethodReturnTypeExtension
106 |
107 | -
108 | class: PHPStan\Type\Nette\FormContainerValuesDynamicReturnTypeExtension
109 | tags:
110 | - phpstan.broker.dynamicMethodReturnTypeExtension
111 |
112 | -
113 | class: PHPStan\Rule\Nette\PresenterInjectedPropertiesExtension
114 | tags:
115 | - phpstan.properties.readWriteExtension
116 |
117 | -
118 | class: PHPStan\Type\Nette\StringsMatchAllDynamicReturnTypeExtension
119 | tags:
120 | - phpstan.broker.dynamicStaticMethodReturnTypeExtension
121 |
122 | -
123 | class: PHPStan\Type\Nette\StringsMatchDynamicReturnTypeExtension
124 | tags:
125 | - phpstan.broker.dynamicStaticMethodReturnTypeExtension
126 |
127 | -
128 | class: PHPStan\Type\Nette\StringsReplaceCallbackClosureTypeExtension
129 | tags:
130 | - phpstan.staticMethodParameterClosureTypeExtension
131 |
--------------------------------------------------------------------------------
/rules.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | methodsThrowingExceptions:
3 | Nette\Application\UI\Presenter:
4 | redirectUrl: Nette\Application\AbortException
5 | sendJson: Nette\Application\AbortException
6 | sendResponse: Nette\Application\AbortException
7 | terminate: Nette\Application\AbortException
8 | forward: Nette\Application\AbortException
9 | Nette\Application\UI\Component:
10 | redirect: Nette\Application\AbortException
11 | redirectPermanent: Nette\Application\AbortException
12 | error: Nette\Application\BadRequestException
13 |
14 | parametersSchema:
15 | methodsThrowingExceptions: arrayOf(arrayOf(string()))
16 |
17 | rules:
18 | - PHPStan\Rule\Nette\DoNotExtendNetteObjectRule
19 | - PHPStan\Rule\Nette\RegularExpressionPatternRule
20 |
21 | services:
22 | -
23 | class: PHPStan\Rule\Nette\RethrowExceptionRule
24 | arguments:
25 | methods: %methodsThrowingExceptions%
26 | tags:
27 | - phpstan.rules.rule
28 |
--------------------------------------------------------------------------------
/src/Reflection/Nette/HtmlClassReflectionExtension.php:
--------------------------------------------------------------------------------
1 | is('Nette\Utils\Html');
17 | }
18 |
19 | public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
20 | {
21 | return new HtmlMethodReflection($methodName, $classReflection);
22 | }
23 |
24 | public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
25 | {
26 | return $classReflection->is('Nette\Utils\Html');
27 | }
28 |
29 | public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
30 | {
31 | return new HtmlPropertyReflection($classReflection);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Reflection/Nette/HtmlMethodReflection.php:
--------------------------------------------------------------------------------
1 | name = $name;
27 | $this->declaringClass = $declaringClass;
28 | }
29 |
30 | public function getDeclaringClass(): ClassReflection
31 | {
32 | return $this->declaringClass;
33 | }
34 |
35 | public function getPrototype(): ClassMemberReflection
36 | {
37 | return $this;
38 | }
39 |
40 | public function isStatic(): bool
41 | {
42 | return false;
43 | }
44 |
45 | public function getVariants(): array
46 | {
47 | return [
48 | new FunctionVariant(
49 | TemplateTypeMap::createEmpty(),
50 | TemplateTypeMap::createEmpty(),
51 | [],
52 | true,
53 | substr($this->name, 0, 3) === 'get' ? new MixedType() : new ObjectType('Nette\Utils\Html'),
54 | ),
55 | ];
56 | }
57 |
58 | public function isPrivate(): bool
59 | {
60 | return false;
61 | }
62 |
63 | public function isPublic(): bool
64 | {
65 | return true;
66 | }
67 |
68 | public function getName(): string
69 | {
70 | return $this->name;
71 | }
72 |
73 | public function isDeprecated(): TrinaryLogic
74 | {
75 | return TrinaryLogic::createNo();
76 | }
77 |
78 | public function getDeprecatedDescription(): ?string
79 | {
80 | return null;
81 | }
82 |
83 | public function isFinal(): TrinaryLogic
84 | {
85 | return TrinaryLogic::createNo();
86 | }
87 |
88 | public function isInternal(): TrinaryLogic
89 | {
90 | return TrinaryLogic::createNo();
91 | }
92 |
93 | public function getThrowType(): Type
94 | {
95 | return new VoidType();
96 | }
97 |
98 | public function getDocComment(): ?string
99 | {
100 | return null;
101 | }
102 |
103 | public function hasSideEffects(): TrinaryLogic
104 | {
105 | return TrinaryLogic::createYes();
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/Reflection/Nette/HtmlPropertyReflection.php:
--------------------------------------------------------------------------------
1 | declaringClass = $declaringClass;
21 | $this->type = new MixedType();
22 | }
23 |
24 | public function getDeclaringClass(): ClassReflection
25 | {
26 | return $this->declaringClass;
27 | }
28 |
29 | public function isStatic(): bool
30 | {
31 | return false;
32 | }
33 |
34 | public function isPrivate(): bool
35 | {
36 | return false;
37 | }
38 |
39 | public function isPublic(): bool
40 | {
41 | return true;
42 | }
43 |
44 | public function isReadable(): bool
45 | {
46 | return true;
47 | }
48 |
49 | public function isWritable(): bool
50 | {
51 | return true;
52 | }
53 |
54 | public function isDeprecated(): TrinaryLogic
55 | {
56 | return TrinaryLogic::createNo();
57 | }
58 |
59 | public function getDeprecatedDescription(): ?string
60 | {
61 | return null;
62 | }
63 |
64 | public function isInternal(): TrinaryLogic
65 | {
66 | return TrinaryLogic::createNo();
67 | }
68 |
69 | public function getDocComment(): ?string
70 | {
71 | return null;
72 | }
73 |
74 | public function getReadableType(): Type
75 | {
76 | return $this->type;
77 | }
78 |
79 | public function getWritableType(): Type
80 | {
81 | return $this->type;
82 | }
83 |
84 | public function canChangeTypeAfterAssignment(): bool
85 | {
86 | return true;
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/Reflection/Nette/NetteObjectClassReflectionExtension.php:
--------------------------------------------------------------------------------
1 | inheritsFromNetteObject($classReflection->getNativeReflection())) {
27 | return false;
28 | }
29 |
30 | $getterMethod = $this->getMethodByProperty($classReflection, $propertyName);
31 | if ($getterMethod === null) {
32 | return false;
33 | }
34 | if ($getterMethod->isStatic()) {
35 | return false;
36 | }
37 |
38 | return $getterMethod->isPublic();
39 | }
40 |
41 | private function getMethodByProperty(ClassReflection $classReflection, string $propertyName): ?MethodReflection
42 | {
43 | $getterMethodName = sprintf('get%s', ucfirst($propertyName));
44 | if (!$classReflection->hasNativeMethod($getterMethodName)) {
45 | return null;
46 | }
47 |
48 | return $classReflection->getNativeMethod($getterMethodName);
49 | }
50 |
51 | public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
52 | {
53 | /** @var MethodReflection $getterMethod */
54 | $getterMethod = $this->getMethodByProperty($classReflection, $propertyName);
55 | return new NetteObjectPropertyReflection($classReflection, $getterMethod->getVariants()[0]->getReturnType());
56 | }
57 |
58 | public function hasMethod(ClassReflection $classReflection, string $methodName): bool
59 | {
60 | $traitNames = $this->getTraitNames($classReflection->getNativeReflection());
61 | if (!in_array('Nette\SmartObject', $traitNames, true) && !$this->inheritsFromNetteObject($classReflection->getNativeReflection())) {
62 | return false;
63 | }
64 |
65 | if (substr($methodName, 0, 2) !== 'on' || strlen($methodName) <= 2) {
66 | return false;
67 | }
68 |
69 | return $classReflection->hasNativeProperty($methodName) && $classReflection->getNativeProperty($methodName)->isPublic();
70 | }
71 |
72 | public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
73 | {
74 | return new NetteObjectEventListenerMethodReflection($methodName, $classReflection);
75 | }
76 |
77 | /**
78 | * @param ReflectionClass|ReflectionEnum $class
79 | * @return string[]
80 | */
81 | private function getTraitNames($class): array
82 | {
83 | $traitNames = $class->getTraitNames();
84 | while ($class->getParentClass() !== false) {
85 | $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames())));
86 | $class = $class->getParentClass();
87 | }
88 |
89 | return $traitNames;
90 | }
91 |
92 | /**
93 | * @param ReflectionClass|ReflectionEnum $class
94 | */
95 | private function inheritsFromNetteObject($class): bool
96 | {
97 | $class = $class->getParentClass();
98 | while ($class !== false) {
99 | if (in_array($class->getName(), [ // @phpstan-ignore-line
100 | 'Nette\Object',
101 | 'Nette\LegacyObject',
102 | ], true)) {
103 | return true;
104 | }
105 | $class = $class->getParentClass();
106 | }
107 |
108 | return false;
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/Reflection/Nette/NetteObjectEventListenerMethodReflection.php:
--------------------------------------------------------------------------------
1 | name = $name;
24 | $this->declaringClass = $declaringClass;
25 | }
26 |
27 | public function getName(): string
28 | {
29 | return $this->name;
30 | }
31 |
32 | public function getDeclaringClass(): ClassReflection
33 | {
34 | return $this->declaringClass;
35 | }
36 |
37 | public function getPrototype(): ClassMemberReflection
38 | {
39 | return $this;
40 | }
41 |
42 | public function isStatic(): bool
43 | {
44 | return false;
45 | }
46 |
47 | public function getVariants(): array
48 | {
49 | return [
50 | new FunctionVariant(
51 | TemplateTypeMap::createEmpty(),
52 | TemplateTypeMap::createEmpty(),
53 | [],
54 | true,
55 | new VoidType(),
56 | ),
57 | ];
58 | }
59 |
60 | public function isPrivate(): bool
61 | {
62 | return false;
63 | }
64 |
65 | public function isPublic(): bool
66 | {
67 | return true;
68 | }
69 |
70 | public function isDeprecated(): TrinaryLogic
71 | {
72 | return TrinaryLogic::createNo();
73 | }
74 |
75 | public function getDeprecatedDescription(): ?string
76 | {
77 | return null;
78 | }
79 |
80 | public function isFinal(): TrinaryLogic
81 | {
82 | return TrinaryLogic::createNo();
83 | }
84 |
85 | public function isInternal(): TrinaryLogic
86 | {
87 | return TrinaryLogic::createNo();
88 | }
89 |
90 | public function getThrowType(): ?Type
91 | {
92 | return null;
93 | }
94 |
95 | public function getDocComment(): ?string
96 | {
97 | return null;
98 | }
99 |
100 | public function hasSideEffects(): TrinaryLogic
101 | {
102 | return TrinaryLogic::createYes();
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/Reflection/Nette/NetteObjectPropertyReflection.php:
--------------------------------------------------------------------------------
1 | declaringClass = $declaringClass;
20 | $this->type = $type;
21 | }
22 |
23 | public function getDeclaringClass(): ClassReflection
24 | {
25 | return $this->declaringClass;
26 | }
27 |
28 | public function isStatic(): bool
29 | {
30 | return false;
31 | }
32 |
33 | public function isPrivate(): bool
34 | {
35 | return false;
36 | }
37 |
38 | public function isPublic(): bool
39 | {
40 | return true;
41 | }
42 |
43 | public function isReadable(): bool
44 | {
45 | return true;
46 | }
47 |
48 | public function isWritable(): bool
49 | {
50 | return true;
51 | }
52 |
53 | public function isDeprecated(): TrinaryLogic
54 | {
55 | return TrinaryLogic::createNo();
56 | }
57 |
58 | public function getDeprecatedDescription(): ?string
59 | {
60 | return null;
61 | }
62 |
63 | public function isInternal(): TrinaryLogic
64 | {
65 | return TrinaryLogic::createNo();
66 | }
67 |
68 | public function getDocComment(): ?string
69 | {
70 | return null;
71 | }
72 |
73 | public function getReadableType(): Type
74 | {
75 | return $this->type;
76 | }
77 |
78 | public function getWritableType(): Type
79 | {
80 | return $this->type;
81 | }
82 |
83 | public function canChangeTypeAfterAssignment(): bool
84 | {
85 | return true;
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/Rule/Nette/DoNotExtendNetteObjectRule.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class DoNotExtendNetteObjectRule implements Rule
17 | {
18 |
19 | public function getNodeType(): string
20 | {
21 | return InClassNode::class;
22 | }
23 |
24 | public function processNode(Node $node, Scope $scope): array
25 | {
26 | $classReflection = $node->getClassReflection();
27 | $parentClass = $classReflection->getNativeReflection()->getParentClass();
28 | if ($parentClass !== false && in_array($parentClass->getName(), [ // @phpstan-ignore-line
29 | 'Nette\Object',
30 | 'Nette\LegacyObject',
31 | ], true)) {
32 | return [
33 | RuleErrorBuilder::message(sprintf(
34 | "Class %s extends %s - it's better to use %s trait.",
35 | $classReflection->getDisplayName(),
36 | 'Nette\Object',
37 | 'Nette\SmartObject',
38 | ))->identifier('class.extendsNetteObject')->build(),
39 | ];
40 | }
41 |
42 | return [];
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/Rule/Nette/PresenterInjectedPropertiesExtension.php:
--------------------------------------------------------------------------------
1 | isInitialized($property, $propertyName);
20 | }
21 |
22 | public function isInitialized(PropertyReflection $property, string $propertyName): bool
23 | {
24 | return $property->isPublic() &&
25 | strpos($property->getDocComment() ?? '', '@inject') !== false;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/Rule/Nette/RegularExpressionPatternRule.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class RegularExpressionPatternRule implements Rule
21 | {
22 |
23 | public function getNodeType(): string
24 | {
25 | return StaticCall::class;
26 | }
27 |
28 | public function processNode(Node $node, Scope $scope): array
29 | {
30 | $patterns = $this->extractPatterns($node, $scope);
31 |
32 | $errors = [];
33 | foreach ($patterns as $pattern) {
34 | $errorMessage = $this->validatePattern($pattern);
35 | if ($errorMessage === null) {
36 | continue;
37 | }
38 |
39 | $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))
40 | ->identifier('regexp.pattern')
41 | ->build();
42 | }
43 |
44 | return $errors;
45 | }
46 |
47 | /**
48 | * @return string[]
49 | */
50 | private function extractPatterns(StaticCall $staticCall, Scope $scope): array
51 | {
52 | if (!$staticCall->class instanceof Node\Name || !$staticCall->name instanceof Node\Identifier) {
53 | return [];
54 | }
55 | $caller = $scope->resolveTypeByName($staticCall->class);
56 | if (!(new ObjectType(Strings::class))->isSuperTypeOf($caller)->yes()) {
57 | return [];
58 | }
59 | $methodName = strtolower((string) $staticCall->name);
60 | if (
61 | !in_array($methodName, [
62 | 'split',
63 | 'match',
64 | 'matchall',
65 | 'replace',
66 | ], true)
67 | ) {
68 | return [];
69 | }
70 |
71 | if (!isset($staticCall->getArgs()[1])) {
72 | return [];
73 | }
74 | $patternNode = $staticCall->getArgs()[1]->value;
75 | $patternType = $scope->getType($patternNode);
76 |
77 | $patternStrings = [];
78 |
79 | foreach ($patternType->getConstantStrings() as $constantStringType) {
80 | $patternStrings[] = $constantStringType->getValue();
81 | }
82 |
83 | foreach ($patternType->getConstantArrays() as $constantArrayType) {
84 | if ($methodName !== 'replace') {
85 | continue;
86 | }
87 |
88 | foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) {
89 | foreach ($arrayKeyType->getConstantStrings() as $constantString) {
90 | $patternStrings[] = $constantString->getValue();
91 | }
92 | }
93 | }
94 |
95 | return $patternStrings;
96 | }
97 |
98 | private function validatePattern(string $pattern): ?string
99 | {
100 | try {
101 | Strings::match('', $pattern);
102 | } catch (RegexpException $e) {
103 | return $e->getMessage();
104 | }
105 |
106 | return null;
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/src/Rule/Nette/RethrowExceptionRule.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | class RethrowExceptionRule implements Rule
28 | {
29 |
30 | /** @var array */
31 | private array $methods;
32 |
33 | /**
34 | * @param string[][] $methods
35 | */
36 | public function __construct(array $methods)
37 | {
38 | $this->methods = $methods;
39 | }
40 |
41 | public function getNodeType(): string
42 | {
43 | return TryCatch::class;
44 | }
45 |
46 | public function processNode(Node $node, Scope $scope): array
47 | {
48 | $hasGeneralCatch = false;
49 | foreach ($node->catches as $catch) {
50 | foreach ($catch->types as $type) {
51 | $typeClass = (string) $type;
52 | if ($typeClass === 'Exception' || $typeClass === Throwable::class) {
53 | $hasGeneralCatch = true;
54 | break 2;
55 | }
56 | }
57 | }
58 | if (!$hasGeneralCatch) {
59 | return [];
60 | }
61 |
62 | $exceptions = $this->getExceptionTypes($scope, $node->stmts);
63 | if (count($exceptions) === 0) {
64 | return [];
65 | }
66 |
67 | $messages = [];
68 | foreach ($exceptions as $exceptionName) {
69 | $exceptionType = new ObjectType($exceptionName);
70 | foreach ($node->catches as $catch) {
71 | $caughtType = TypeCombinator::union(...array_map(static fn (Name $class): ObjectType => new ObjectType((string) $class), $catch->types));
72 | if (!$caughtType->isSuperTypeOf($exceptionType)->yes()) {
73 | continue;
74 | }
75 | if (
76 | count($catch->stmts) === 1
77 | && $catch->stmts[0] instanceof Node\Stmt\Expression
78 | && $catch->stmts[0]->expr instanceof Node\Expr\Throw_
79 | && $catch->stmts[0]->expr->expr instanceof Variable
80 | && $catch->var !== null
81 | && is_string($catch->var->name)
82 | && is_string($catch->stmts[0]->expr->expr->name)
83 | && $catch->var->name === $catch->stmts[0]->expr->expr->name
84 | ) {
85 | continue 2;
86 | }
87 | }
88 |
89 | $messages[] = RuleErrorBuilder::message(sprintf('Exception %s needs to be rethrown.', $exceptionName))->identifier('nette.rethrowException')->build();
90 | }
91 |
92 | return $messages;
93 | }
94 |
95 | /**
96 | * @param Node|Node[]|scalar $node
97 | * @return string[]
98 | */
99 | private function getExceptionTypes(Scope $scope, $node): array
100 | {
101 | $exceptions = [];
102 | if ($node instanceof Node) {
103 | foreach ($node->getSubNodeNames() as $subNodeName) {
104 | $subNode = $node->{$subNodeName};
105 | $exceptions = array_merge($exceptions, $this->getExceptionTypes($scope, $subNode));
106 | }
107 | if ($node instanceof Node\Expr\MethodCall) {
108 | $methodCalledOn = $scope->getType($node->var);
109 | foreach ($this->methods as $type => $methods) {
110 | if (!$node->name instanceof Node\Identifier) {
111 | continue;
112 | }
113 | if (!(new ObjectType($type))->isSuperTypeOf($methodCalledOn)->yes()) {
114 | continue;
115 | }
116 |
117 | $methodName = strtolower((string) $node->name);
118 | foreach ($methods as $throwingMethodName => $exception) {
119 | if (strtolower($throwingMethodName) !== $methodName) {
120 | continue;
121 | }
122 | $exceptions[] = $exception;
123 | }
124 | }
125 | }
126 | } elseif (is_array($node)) {
127 | foreach ($node as $subNode) {
128 | $exceptions = array_merge($exceptions, $this->getExceptionTypes($scope, $subNode));
129 | }
130 | }
131 |
132 | return array_unique($exceptions);
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/Type/Nette/ComponentGetPresenterDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'getPresenter';
25 | }
26 |
27 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
28 | {
29 | $methodDefinition = ParametersAcceptorSelector::selectFromArgs(
30 | $scope,
31 | $methodCall->getArgs(),
32 | $methodReflection->getVariants(),
33 | );
34 | $defaultReturnType = $methodDefinition->getReturnType();
35 | $firstParameterExists = count($methodDefinition->getParameters()) > 0;
36 |
37 | if (count($methodCall->getArgs()) < 1) {
38 | if (!$firstParameterExists) {
39 | return TypeCombinator::removeNull($defaultReturnType);
40 | }
41 |
42 | return $defaultReturnType;
43 | }
44 |
45 | $paramNeedExpr = $methodCall->getArgs()[0]->value;
46 | $paramNeedType = $scope->getType($paramNeedExpr);
47 |
48 | if ($paramNeedType->isTrue()->yes()) {
49 | return TypeCombinator::removeNull($defaultReturnType);
50 | }
51 | if ($paramNeedType->isFalse()->yes()) {
52 | return TypeCombinator::addNull($defaultReturnType);
53 | }
54 |
55 | return $defaultReturnType;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/Type/Nette/ComponentLookupDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'lookup';
25 | }
26 |
27 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
28 | {
29 | $defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
30 | $scope,
31 | $methodCall->getArgs(),
32 | $methodReflection->getVariants(),
33 | )->getReturnType();
34 | if (count($methodCall->getArgs()) < 2) {
35 | return $defaultReturnType;
36 | }
37 |
38 | $paramNeedExpr = $methodCall->getArgs()[1]->value;
39 | $paramNeedType = $scope->getType($paramNeedExpr);
40 |
41 | if ($paramNeedType->isTrue()->yes()) {
42 | return TypeCombinator::removeNull($defaultReturnType);
43 | }
44 | if ($paramNeedType->isFalse()->yes()) {
45 | return TypeCombinator::addNull($defaultReturnType);
46 | }
47 |
48 | return $defaultReturnType;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Type/Nette/ComponentModelArrayAccessDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'offsetGet';
34 | }
35 |
36 | public function getTypeFromMethodCall(
37 | MethodReflection $methodReflection,
38 | MethodCall $methodCall,
39 | Scope $scope
40 | ): Type
41 | {
42 | $calledOnType = $scope->getType($methodCall->var);
43 | $defaultType = $calledOnType->getMethod('createComponent', $scope)->getVariants()[0]->getReturnType();
44 | $defaultType = TypeCombinator::remove($defaultType, new NullType());
45 | if ($defaultType->isSuperTypeOf(new ObjectType('Nette\ComponentModel\IComponent'))->yes()) {
46 | $defaultType = new MixedType(false, new NullType());
47 | }
48 | $args = $methodCall->getArgs();
49 | if (count($args) < 1) {
50 | return $defaultType;
51 | }
52 |
53 | $argType = $scope->getType($args[0]->value);
54 | if (count($argType->getConstantStrings()) === 0) {
55 | return $defaultType;
56 | }
57 |
58 | $types = [];
59 | foreach ($argType->getConstantStrings() as $constantString) {
60 | $componentName = $constantString->getValue();
61 |
62 | $methodName = sprintf('createComponent%s', ucfirst($componentName));
63 | if (!$calledOnType->hasMethod($methodName)->yes()) {
64 | return $defaultType;
65 | }
66 |
67 | $method = $calledOnType->getMethod($methodName, $scope);
68 |
69 | $types[] = ParametersAcceptorSelector::selectFromArgs(
70 | $scope,
71 | [new Arg(new String_($componentName))],
72 | $method->getVariants(),
73 | )->getReturnType();
74 | }
75 |
76 | return TypeCombinator::union(...$types);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/Type/Nette/ComponentModelDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'getComponent';
34 | }
35 |
36 | public function getTypeFromMethodCall(
37 | MethodReflection $methodReflection,
38 | MethodCall $methodCall,
39 | Scope $scope
40 | ): Type
41 | {
42 | $calledOnType = $scope->getType($methodCall->var);
43 | $defaultType = $calledOnType->getMethod('createComponent', $scope)->getVariants()[0]->getReturnType();
44 | if ($defaultType->isSuperTypeOf(new ObjectType('Nette\ComponentModel\IComponent'))->yes()) {
45 | $defaultType = new MixedType();
46 | }
47 | $args = $methodCall->getArgs();
48 | if (count($args) < 1) {
49 | return $defaultType;
50 | }
51 |
52 | $throw = true;
53 | if (isset($args[1])) {
54 | $throwType = $scope->getType($args[1]->value);
55 | if (!$throwType->isTrue()->yes()) {
56 | $throw = false;
57 | }
58 | }
59 |
60 | if ($throw) {
61 | $defaultType = TypeCombinator::remove($defaultType, new NullType());
62 | }
63 |
64 | $argType = $scope->getType($args[0]->value);
65 | if (count($argType->getConstantStrings()) === 0) {
66 | return $defaultType;
67 | }
68 |
69 | $types = [];
70 | foreach ($argType->getConstantStrings() as $constantString) {
71 | $componentName = $constantString->getValue();
72 |
73 | $methodName = sprintf('createComponent%s', ucfirst($componentName));
74 | if (!$calledOnType->hasMethod($methodName)->yes()) {
75 | return $defaultType;
76 | }
77 |
78 | $method = $calledOnType->getMethod($methodName, $scope);
79 |
80 | $types[] = ParametersAcceptorSelector::selectFromArgs(
81 | $scope,
82 | [new Arg(new String_($componentName))],
83 | $method->getVariants(),
84 | )->getReturnType();
85 | }
86 |
87 | return TypeCombinator::union(...$types);
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/Type/Nette/FormContainerUnsafeValuesDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'getUnsafeValues';
27 | }
28 |
29 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
30 | {
31 | if (count($methodCall->getArgs()) === 0) {
32 | return null;
33 | }
34 |
35 | $arg = $methodCall->getArgs()[0]->value;
36 | $scopedType = $scope->getType($arg);
37 | if ($scopedType->isNull()->yes()) {
38 | return new ObjectType('Nette\Utils\ArrayHash');
39 | }
40 |
41 | if (count($scopedType->getConstantStrings()) === 1 && $scopedType->getConstantStrings()[0]->getValue() === 'array') {
42 | return new ArrayType(new StringType(), new MixedType());
43 | }
44 |
45 | return null;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'getValues';
27 | }
28 |
29 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
30 | {
31 | if (count($methodCall->getArgs()) === 0) {
32 | return new ObjectType('Nette\Utils\ArrayHash');
33 | }
34 |
35 | $arg = $methodCall->getArgs()[0]->value;
36 | $scopedType = $scope->getType($arg);
37 | if ($scopedType->isTrue()->yes()) {
38 | return new ArrayType(new StringType(), new MixedType());
39 | }
40 | if ($scopedType->isFalse()->yes()) {
41 | return new ObjectType('Nette\Utils\ArrayHash');
42 | }
43 |
44 | return null;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Type/Nette/FormsBaseControlDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getDeclaringClass()->getName() === 'Nette\Forms\Controls\BaseControl';
26 | }
27 |
28 | public function getTypeFromMethodCall(
29 | MethodReflection $methodReflection,
30 | MethodCall $methodCall,
31 | Scope $scope
32 | ): Type
33 | {
34 | $returnType = ParametersAcceptorSelector::selectFromArgs(
35 | $scope,
36 | $methodCall->getArgs(),
37 | $methodReflection->getVariants(),
38 | )->getReturnType();
39 | $referencedClasses = $returnType->getReferencedClasses();
40 | if (
41 | count($referencedClasses) === 1
42 | && $referencedClasses[0] === 'Nette\Forms\Controls\BaseControl'
43 | ) {
44 | return $scope->getType($methodCall->var);
45 | }
46 |
47 | return $returnType;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/Type/Nette/PresenterGetSessionReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'getSession';
24 | }
25 |
26 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
27 | {
28 | if (count($methodCall->getArgs()) === 0 || $scope->getType($methodCall->getArgs()[0]->value)->isNull()->yes()) {
29 | return new ObjectType('Nette\Http\Session');
30 | }
31 |
32 | return new ObjectType('Nette\Http\SessionSection');
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/Type/Nette/ServiceLocatorDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName(), [
28 | 'getByType',
29 | 'createInstance',
30 | 'getService',
31 | 'createService',
32 | ], true);
33 | }
34 |
35 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
36 | {
37 | $mixedType = new MixedType();
38 | if (in_array($methodReflection->getName(), [
39 | 'getService',
40 | 'createService',
41 | ], true)) {
42 | return $mixedType;
43 | }
44 | if (count($methodCall->getArgs()) === 0) {
45 | return $mixedType;
46 | }
47 | $argType = $scope->getType($methodCall->getArgs()[0]->value);
48 | if (count($argType->getConstantStrings()) === 0) {
49 | return $mixedType;
50 | }
51 |
52 | $types = [];
53 | foreach ($argType->getConstantStrings() as $constantString) {
54 | $type = new ObjectType($constantString->getValue());
55 | if (
56 | $methodReflection->getName() === 'getByType'
57 | && count($methodCall->getArgs()) >= 2
58 | ) {
59 | $throwType = $scope->getType($methodCall->getArgs()[1]->value);
60 | if (!$throwType->isTrue()->yes()) {
61 | $type = TypeCombinator::addNull($type);
62 | }
63 | }
64 |
65 | $types[] = $type;
66 | }
67 |
68 | return TypeCombinator::union(...$types);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/Type/Nette/StringsMatchAllDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | regexArrayShapeMatcher = $regexArrayShapeMatcher;
30 | }
31 |
32 | public function getClass(): string
33 | {
34 | return Strings::class;
35 | }
36 |
37 | public function isStaticMethodSupported(MethodReflection $methodReflection): bool
38 | {
39 | return $methodReflection->getName() === 'matchAll';
40 | }
41 |
42 | public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
43 | {
44 | $args = $methodCall->getArgs();
45 | $patternArg = $args[1] ?? null;
46 |
47 | if ($patternArg === null) {
48 | return null;
49 | }
50 |
51 | return $this->regexArrayShapeMatcher->matchAllExpr(
52 | $patternArg->value,
53 | $this->resolveFlagsType($args, $scope),
54 | TrinaryLogic::createYes(),
55 | $scope,
56 | );
57 | }
58 |
59 | /**
60 | * @param array $args
61 | */
62 | private function resolveFlagsType(array $args, Scope $scope): ConstantIntegerType
63 | {
64 | if (!array_key_exists(2, $args)) {
65 | return new ConstantIntegerType(PREG_SET_ORDER);
66 | }
67 |
68 | $captureOffsetType = $scope->getType($args[2]->value);
69 |
70 | if ($captureOffsetType instanceof ConstantIntegerType) {
71 | $captureOffset = $captureOffsetType->getValue();
72 | $flags = ($captureOffset & PREG_PATTERN_ORDER) === PREG_PATTERN_ORDER ? $captureOffset : ($captureOffset | PREG_SET_ORDER);
73 |
74 | return new ConstantIntegerType($flags);
75 | }
76 |
77 | $unmatchedAsNullType = array_key_exists(4, $args) ? $scope->getType($args[4]->value) : new ConstantBooleanType(false);
78 | $patternOrderType = array_key_exists(5, $args) ? $scope->getType($args[5]->value) : new ConstantBooleanType(false);
79 |
80 | $captureOffset = $captureOffsetType->isTrue()->yes();
81 | $unmatchedAsNull = $unmatchedAsNullType->isTrue()->yes();
82 | $patternOrder = $patternOrderType->isTrue()->yes();
83 |
84 | return new ConstantIntegerType(
85 | ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0),
86 | );
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/Type/Nette/StringsMatchDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | regexArrayShapeMatcher = $regexArrayShapeMatcher;
30 | }
31 |
32 | public function getClass(): string
33 | {
34 | return Strings::class;
35 | }
36 |
37 | public function isStaticMethodSupported(MethodReflection $methodReflection): bool
38 | {
39 | return $methodReflection->getName() === 'match';
40 | }
41 |
42 | public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
43 | {
44 | $args = $methodCall->getArgs();
45 | $patternArg = $args[1] ?? null;
46 |
47 | if ($patternArg === null) {
48 | return null;
49 | }
50 |
51 | $arrayShape = $this->regexArrayShapeMatcher->matchExpr(
52 | $patternArg->value,
53 | $this->resolveFlagsType($args, $scope),
54 | TrinaryLogic::createYes(),
55 | $scope,
56 | );
57 |
58 | if ($arrayShape === null) {
59 | return null;
60 | }
61 |
62 | return TypeCombinator::union($arrayShape, new NullType());
63 | }
64 |
65 | /**
66 | * @param array $args
67 | */
68 | private function resolveFlagsType(array $args, Scope $scope): ConstantIntegerType
69 | {
70 | if (!array_key_exists(2, $args)) {
71 | return new ConstantIntegerType(0);
72 | }
73 |
74 | $captureOffsetType = $scope->getType($args[2]->value);
75 |
76 | if ($captureOffsetType instanceof ConstantIntegerType) {
77 | return $captureOffsetType;
78 | }
79 |
80 | $unmatchedAsNullType = array_key_exists(4, $args) ? $scope->getType($args[4]->value) : new ConstantBooleanType(false);
81 |
82 | $captureOffset = $captureOffsetType->isTrue()->yes();
83 | $unmatchedAsNull = $unmatchedAsNullType->isTrue()->yes();
84 |
85 | return new ConstantIntegerType(
86 | ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0),
87 | );
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/Type/Nette/StringsReplaceCallbackClosureTypeExtension.php:
--------------------------------------------------------------------------------
1 | regexArrayShapeMatcher = $regexArrayShapeMatcher;
32 | }
33 |
34 | public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
35 | {
36 | return $methodReflection->getDeclaringClass()->getName() === Strings::class
37 | && $methodReflection->getName() === 'replace'
38 | && $parameter->getName() === 'replacement';
39 | }
40 |
41 | public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
42 | {
43 | $args = $methodCall->getArgs();
44 | $patternArg = $args[1] ?? null;
45 | $replacementArg = $args[2] ?? null;
46 |
47 | if ($patternArg === null || $replacementArg === null) {
48 | return null;
49 | }
50 |
51 | $replacementType = $scope->getType($replacementArg->value);
52 |
53 | if (!$replacementType->isCallable()->yes()) {
54 | return null;
55 | }
56 |
57 | $matchesType = $this->regexArrayShapeMatcher->matchExpr(
58 | $patternArg->value,
59 | $this->resolveFlagsType($args, $scope),
60 | TrinaryLogic::createYes(),
61 | $scope,
62 | );
63 |
64 | if ($matchesType === null) {
65 | return null;
66 | }
67 |
68 | return new ClosureType(
69 | [
70 | $this->createParameterReflectionClass($parameter, $matchesType),
71 | ],
72 | new StringType(),
73 | );
74 | }
75 |
76 | /**
77 | * @param array $args
78 | */
79 | private function resolveFlagsType(array $args, Scope $scope): ConstantIntegerType
80 | {
81 | $captureOffsetType = array_key_exists(4, $args) ? $scope->getType($args[4]->value) : new ConstantBooleanType(false);
82 | $unmatchedAsNullType = array_key_exists(5, $args) ? $scope->getType($args[5]->value) : new ConstantBooleanType(false);
83 |
84 | $captureOffset = $captureOffsetType->isTrue()->yes();
85 | $unmatchedAsNull = $unmatchedAsNullType->isTrue()->yes();
86 |
87 | return new ConstantIntegerType(($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0));
88 | }
89 |
90 | private function createParameterReflectionClass(ParameterReflection $parameter, Type $matchesType): ParameterReflection
91 | {
92 | return new class($parameter, $matchesType) implements ParameterReflection {
93 |
94 | private ParameterReflection $parameter;
95 |
96 | private Type $matchesType;
97 |
98 | public function __construct(
99 | ParameterReflection $parameter,
100 | Type $matchesType
101 | )
102 | {
103 | $this->parameter = $parameter;
104 | $this->matchesType = $matchesType;
105 | }
106 |
107 | public function getName(): string
108 | {
109 | return $this->parameter->getName();
110 | }
111 |
112 | public function isOptional(): bool
113 | {
114 | return $this->parameter->isOptional();
115 | }
116 |
117 | public function getType(): Type
118 | {
119 | return $this->matchesType;
120 | }
121 |
122 | public function passedByReference(): PassedByReference
123 | {
124 | return $this->parameter->passedByReference();
125 | }
126 |
127 | public function isVariadic(): bool
128 | {
129 | return $this->parameter->isVariadic();
130 | }
131 |
132 | public function getDefaultValue(): ?Type
133 | {
134 | return $this->parameter->getDefaultValue();
135 | }
136 |
137 | };
138 | }
139 |
140 | }
141 |
--------------------------------------------------------------------------------
/stubs/Application/Routers/RouteList.stub:
--------------------------------------------------------------------------------
1 |
7 | * @phpstan-implements \ArrayAccess
8 | */
9 | class RouteList implements \ArrayAccess, \IteratorAggregate
10 | {
11 | /**
12 | * @phpstan-return \ArrayIterator
13 | */
14 | public function getIterator(): \ArrayIterator
15 | {
16 | // nothing
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/stubs/Application/UI/Component.stub:
--------------------------------------------------------------------------------
1 |
9 | */
10 | abstract class Component implements \ArrayAccess
11 | {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/stubs/Application/UI/Multiplier.stub:
--------------------------------------------------------------------------------
1 | ) : T $factory
12 | */
13 | public function __construct(callable $factory);
14 |
15 | /**
16 | * @return T
17 | */
18 | protected function createComponent(string $name): \Nette\ComponentModel\IComponent;
19 | }
20 |
--------------------------------------------------------------------------------
/stubs/Application/UI/Presenter.stub:
--------------------------------------------------------------------------------
1 | |null $dependencies
10 | * @param-immediately-invoked-callable $generator
11 | */
12 | public function load(mixed $key, ?callable $generator = null, ?array $dependencies = null): mixed
13 | {
14 | }
15 |
16 | /**
17 | * @param array $keys
18 | * @return array
19 | * @param-immediately-invoked-callable $generator
20 | */
21 | public function bulkLoad(array $keys, ?callable $generator = null): array
22 | {
23 | }
24 |
25 | /**
26 | * @param-immediately-invoked-callable $function
27 | */
28 | public function call(callable $function): mixed
29 | {
30 | }
31 |
32 | /**
33 | * @param array|null $dependencies
34 | * @param-immediately-invoked-callable $function
35 | */
36 | public function wrap(callable $function, ?array $dependencies = null): \Closure
37 | {
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/stubs/ComponentModel/Component.stub:
--------------------------------------------------------------------------------
1 | $filterType
11 | * @phpstan-return ($filterType is null ? \Iterator : \Iterator)
12 | */
13 | public function getComponents(bool $deep = false, string $filterType = null): \Iterator
14 | {
15 | // nothing
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/stubs/ComponentModel/IComponent.stub:
--------------------------------------------------------------------------------
1 |
7 | * @phpstan-extends \ArrayAccess
8 | */
9 | interface IRow extends \Traversable, \ArrayAccess
10 | {
11 |
12 | }
13 |
14 | /**
15 | * @phpstan-extends \Traversable
16 | */
17 | interface IRowContainer extends \Traversable
18 | {
19 |
20 | }
21 |
22 | /**
23 | * @phpstan-implements \Iterator
24 | */
25 | class ResultSet implements \Iterator
26 | {
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/stubs/Database/Table/ActiveRow.stub:
--------------------------------------------------------------------------------
1 | >
9 | */
10 | class ActiveRow implements \IteratorAggregate
11 | {
12 | /**
13 | * @phpstan-return \ArrayIterator>
14 | */
15 | public function getIterator(): \ArrayIterator
16 | {
17 | // nothing
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/stubs/Database/Table/Selection.stub:
--------------------------------------------------------------------------------
1 |
8 | * @phpstan-implements \ArrayAccess
9 | */
10 | class Selection implements \Iterator, \ArrayAccess
11 | {
12 | /**
13 | * @phpstan-param positive-int|0|null $limit
14 | * @phpstan-param positive-int|0|null $offset
15 | * @return static
16 | */
17 | public function limit(?int $limit, int $offset = null)
18 | {
19 | }
20 |
21 | /**
22 | * @phpstan-param positive-int|0 $page
23 | * @phpstan-param positive-int|0 $itemsPerPage
24 | * @param int $numOfPages [optional]
25 | * @return static
26 | */
27 | public function page(int $page, int $itemsPerPage, &$numOfPages = null)
28 | {
29 | }
30 |
31 | /**
32 | * @param string|array $condition
33 | * @param mixed $params
34 | * @return static
35 | */
36 | public function where($condition, ...$params)
37 | {
38 | }
39 |
40 | /**
41 | * @param string $column
42 | * @return positive-int|0
43 | */
44 | public function count($column = null)
45 | {
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/stubs/Forms/Container.stub:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Container implements \ArrayAccess
14 | {
15 |
16 | /** @var array */
17 | public $onValidate;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/stubs/Forms/Form.stub:
--------------------------------------------------------------------------------
1 | */
9 | public $onSuccess;
10 |
11 | /** @var array */
12 | public $onError;
13 |
14 | /** @var array */
15 | public $onSubmit;
16 |
17 | /** @var array */
18 | public $onRender;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/stubs/Forms/Rules.stub:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Rules implements \IteratorAggregate
14 | {
15 |
16 | /**
17 | * @return \ArrayIterator
18 | */
19 | public function getIterator(): \ArrayIterator
20 | {
21 | // nothing
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/stubs/Http/FileUpload.stub:
--------------------------------------------------------------------------------
1 |
7 | * @implements \ArrayAccess
8 | */
9 | class SessionSection implements \IteratorAggregate, \ArrayAccess
10 | {
11 |
12 | /**
13 | * @return \Iterator
14 | */
15 | public function getIterator(): \Iterator
16 | {
17 | // nothing
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/stubs/Routing/Router.stub:
--------------------------------------------------------------------------------
1 |
7 | * @implements \ArrayAccess
8 | */
9 | class ArrayHash implements \ArrayAccess, \IteratorAggregate
10 | {
11 |
12 | /**
13 | * @return \RecursiveArrayIterator
14 | */
15 | public function getIterator(): \RecursiveArrayIterator
16 | {
17 | // nothing
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/stubs/Utils/Arrays.stub:
--------------------------------------------------------------------------------
1 | $array
12 | * @param callable(V, K, array): bool $callback
13 | * @param-immediately-invoked-callable $callback
14 | */
15 | public static function some(iterable $array, callable $callback): bool
16 | {
17 | }
18 |
19 | /**
20 | * @template K of array-key
21 | * @template V
22 | * @param array $array
23 | * @param callable(V, K, array): bool $callback
24 | * @param-immediately-invoked-callable $callback
25 | */
26 | public static function every(iterable $array, callable $callback): bool
27 | {
28 | }
29 |
30 | /**
31 | * @template K of array-key
32 | * @template V
33 | * @template R
34 | * @param array $array
35 | * @param callable(V, K, array): R $callback
36 | * @return array
37 | * @param-immediately-invoked-callable $callback
38 | */
39 | public static function map(iterable $array, callable $callback): array
40 | {
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/stubs/Utils/Callback.stub:
--------------------------------------------------------------------------------
1 |
7 | * @implements \ArrayAccess
8 | */
9 | class Html implements \ArrayAccess, \IteratorAggregate
10 | {
11 |
12 | /**
13 | * @return \ArrayIterator
14 | */
15 | public function getIterator(): \ArrayIterator
16 | {
17 | // nothing
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/stubs/Utils/Paginator.stub:
--------------------------------------------------------------------------------
1 |