Skip to content

Commit b749e9c

Browse files
committed
fixed PHPStan errors
1 parent 9e03625 commit b749e9c

18 files changed

Lines changed: 258 additions & 46 deletions

phpstan.neon

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,187 @@
11
parameters:
2-
level: 5
2+
level: 8
33

44
paths:
55
- src
6+
7+
excludePaths:
8+
- src/Bridges/FormsLatte/FormMacros.php
9+
- src/compatibility.php
10+
11+
ignoreErrors:
12+
- # Latte nodes use new static() by design for extensibility
13+
identifier: new.static
14+
path: src/Bridges/FormsLatte/Nodes/*
15+
count: 6
16+
17+
- # Html properties accept broader types (bool for attribute removal, mixed for flexibility)
18+
identifier: assign.propertyType
19+
message: '#^Property Nette\\Utils\\Html::\$\w+ \([^)]+\) does not accept [^.]+\.$#'
20+
paths:
21+
- src/Forms/Controls/BaseControl.php
22+
- src/Forms/Controls/TextBase.php
23+
- src/Forms/Controls/TextInput.php
24+
- src/Forms/Form.php
25+
26+
- # Form::$renderer and Form::$httpRequest are late-initialized via setters that
27+
# accept nullable arguments but the properties are non-nullable for ergonomics.
28+
identifier: assign.propertyType
29+
message: '#^Property Nette\\Forms\\Form::\$(renderer|httpRequest) \([^)]+\) does not accept [^.]+\.$#'
30+
path: src/Forms/Form.php
31+
count: 2
32+
33+
- # SubmitButton::getScopeForValidation() walks getParent() which is typed as
34+
# Container|Control; lookupPath() exists on Container at runtime.
35+
identifier: method.notFound
36+
message: '#^Call to an undefined method Nette\\Forms\\Container\|Nette\\Forms\\Control::lookupPath\(\)\.$#'
37+
path: src/Forms/Controls/SubmitButton.php
38+
count: 1
39+
40+
- # Nette\Forms\Control interface is intentionally minimal and missing common methods
41+
# (getControl, getLabel, getForm, getOption, setOption, getHtmlName, getName, getParent,
42+
# lookupPath, hasErrors, isRequired, isFilled, isDisabled, addError). In practice all
43+
# Control instances are BaseControl descendants which provide these methods.
44+
identifier: method.notFound
45+
message: '#^Call to an undefined method [\w\\&]*Nette\\Forms\\Control(&[\w\\]+)?::\w+\(\)\.$#'
46+
paths:
47+
- src/Bridges/FormsLatte/Runtime.php
48+
- src/Forms/Blueprint.php
49+
- src/Forms/Container.php
50+
- src/Forms/ControlGroup.php
51+
- src/Forms/Form.php
52+
- src/Forms/Helpers.php
53+
- src/Forms/Rendering/DefaultFormRenderer.php
54+
- src/Forms/Rules.php
55+
- src/Forms/Validator.php
56+
57+
- # Anonymous BaseControl in Blueprint widens return type to match parent contract,
58+
# even though it returns only string literals in this case.
59+
identifier: return.unusedType
60+
path: src/Forms/Blueprint.php
61+
count: 2
62+
63+
- # parent::getControl()/getLabel() returns the wider public contract (Html|string|null),
64+
# but BaseControl implementation deterministically returns Html, so chaining is safe.
65+
identifier: method.nonObject
66+
message: '#^Cannot call method \w+\(\) on Nette\\Utils\\Html\|string(\|null)?\.$#'
67+
paths:
68+
- src/Forms/Controls/Checkbox.php
69+
- src/Forms/Controls/CheckboxList.php
70+
- src/Forms/Controls/ColorPicker.php
71+
- src/Forms/Controls/DateTimeControl.php
72+
- src/Forms/Controls/RadioList.php
73+
74+
- # Same as above - property access on parent::getControl() result.
75+
identifier: property.nonObject
76+
message: '#^Cannot access property \$\w+ on Nette\\Utils\\Html\|string\.$#'
77+
paths:
78+
- src/Forms/Controls/CheckboxList.php
79+
- src/Forms/Controls/MultiSelectBox.php
80+
- src/Forms/Controls/RadioList.php
81+
- src/Forms/Controls/SelectBox.php
82+
- src/Forms/Controls/TextBase.php
83+
84+
- # getControl()/getLabelPart() in children narrow the wider public contract back to Html.
85+
message: '#^Method Nette\\Forms\\Controls\\(TextBase::getControl|Checkbox::getLabelPart)\(\) should return Nette\\Utils\\Html but returns Nette\\Utils\\Html\|string(\|null)?\.$#'
86+
paths:
87+
- src/Forms/Controls/TextBase.php
88+
- src/Forms/Controls/Checkbox.php
89+
90+
- # Generic callable - handlers and validators may have any signature.
91+
identifier: missingType.callable
92+
path: src/Forms/Form.php
93+
count: 1
94+
95+
-
96+
identifier: missingType.callable
97+
path: src/Forms/Rules.php
98+
count: 1
99+
100+
- # Parameters $caption / $key keep untyped for BC - adding native types would break
101+
# existing user overrides of these methods in custom form controls.
102+
identifier: missingType.parameter
103+
message: '#^Method Nette\\Forms\\Controls\\\w+::(getLabel|getControl|getControlPart|getLabelPart)\(\) has parameter \$(caption|key) with no type specified\.$#'
104+
paths:
105+
- src/Forms/Controls/Button.php
106+
- src/Forms/Controls/Checkbox.php
107+
- src/Forms/Controls/CheckboxList.php
108+
- src/Forms/Controls/HiddenField.php
109+
- src/Forms/Controls/RadioList.php
110+
- src/Forms/Controls/SubmitButton.php
111+
112+
- # beforeRender() keeps untyped return for BC - user overrides may not declare void.
113+
identifier: missingType.return
114+
path: src/Forms/Form.php
115+
count: 1
116+
117+
- # Form::getForm() always returns $this, but parent contract is nullable conditional type.
118+
# Adding ?static or conditional PHPDoc would break BC for existing user overrides.
119+
identifier: method.childReturnType
120+
path: src/Forms/Form.php
121+
count: 1
122+
123+
- # $control may be undefined if $controls is empty, but in practice renderPairMulti()
124+
# is only called with non-empty arrays from renderControls().
125+
identifier: variable.undefined
126+
path: src/Forms/Rendering/DefaultFormRenderer.php
127+
count: 1
128+
129+
- # Form controls intentionally narrow setValue() $value from mixed to specific types
130+
# for better type safety - the contravariance is by design.
131+
identifier: method.childParameterType
132+
message: '#^Parameter \#1 \$value \([^)]+\) of method Nette\\Forms\\Controls\\\w+::setValue\(\) should be contravariant with parameter \$value \(mixed\) of method Nette\\Forms\\(Control|Controls\\BaseControl)::setValue\(\)$#'
133+
134+
- # Rules::getCallback() returns callable-array [class, method] without value type.
135+
message: '#^Method Nette\\Forms\\Rules::getCallback\(\) return type has no value type specified in iterable type array\.$#'
136+
path: src/Forms/Rules.php
137+
count: 1
138+
139+
- # DateTimeControl formatHtmlValue accepts broader types from rule args
140+
message: '#^Parameter \#1 \$value of method Nette\\Forms\\Controls\\DateTimeControl::formatHtmlValue\(\)#'
141+
path: src/Forms/Controls/DateTimeControl.php
142+
count: 1
143+
144+
- # Recursive internal calls pass ?string from Helpers::getSingleType() (reflection)
145+
# which may be a class-string OR a built-in type name - PHPStan cannot distinguish.
146+
# Refactoring to satisfy the template would require BC-breaking signature changes
147+
# in the public getValues()/getUntrustedValues() API.
148+
identifier: argument.templateType
149+
message: '#^Unable to resolve the template type T in call to method Nette\\Forms\\Container::(getValues|getUntrustedValues)\(\)$#'
150+
paths:
151+
- src/Forms/Container.php
152+
- src/Forms/Form.php
153+
154+
- # Same root cause as argument.templateType above - ?string from reflection cannot
155+
# be narrowed to class-string<T> at static analysis time.
156+
message: '#^Parameter \#1 \$returnType of method Nette\\Forms\\Container::(getValues|getUntrustedValues)\(\) expects#'
157+
paths:
158+
- src/Forms/Container.php
159+
- src/Forms/Form.php
160+
161+
- # Complex template type T cannot be fully tracked through getValues delegation
162+
message: '#^Method Nette\\Forms\\Container::getValues\(\) should return#'
163+
path: src/Forms/Container.php
164+
count: 1
165+
166+
- # getComponents() returns iterable; narrowing to Iterator not detected
167+
message: '#^Method Nette\\Forms\\Container::getControls\(\) should return#'
168+
path: src/Forms/Container.php
169+
count: 1
170+
171+
-
172+
message: '#^Using nullsafe property access on non\-nullable type Latte\\Compiler\\Nodes\\FragmentNode\. Use \-\> instead\.$#'
173+
identifier: nullsafe.neverNull
174+
count: 1
175+
path: src/Bridges/FormsLatte/Nodes/FieldNNameNode.php
176+
177+
-
178+
message: '#^Call to function is_string\(\) with string will always evaluate to true\.$#'
179+
identifier: function.alreadyNarrowedType
180+
count: 1
181+
path: src/Forms/Controls/ColorPicker.php
182+
183+
-
184+
message: '#^Strict comparison using \=\=\= between '''' and '''' will always evaluate to true\.$#'
185+
identifier: identical.alwaysTrue
186+
count: 1
187+
path: src/Forms/Helpers.php

src/Bridges/FormsDI/FormsExtension.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Nette\Bridges\FormsDI;
99

1010
use Nette;
11-
use function defined, is_object;
11+
use function defined;
1212

1313

1414
/**
@@ -31,7 +31,6 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void
3131
{
3232
$initialize = $this->initialization ?? $class->getMethod('initialize');
3333

34-
assert(is_object($this->config));
3534
foreach ($this->config->messages as $name => $text) {
3635
if (defined('Nette\Forms\Form::' . $name)) {
3736
$initialize->addBody('Nette\Forms\Validator::$messages[Nette\Forms\Form::?] = ?;', [$name, $text]);

src/Bridges/FormsLatte/FormsExtension.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ public function getTags(): array
2727
'inputError' => Nodes\InputErrorNode::create(...),
2828
'formPrint' => Nodes\FormPrintNode::create(...),
2929
'formClassPrint' => Nodes\FormPrintNode::create(...),
30-
'n:name' => fn(Latte\Compiler\Tag $tag) => yield from strtolower($tag->htmlElement->name) === 'form'
31-
? Nodes\FormNNameNode::create($tag)
32-
: Nodes\FieldNNameNode::create($tag),
30+
'n:name' => function (Latte\Compiler\Tag $tag) {
31+
assert($tag->htmlElement !== null);
32+
return yield from strtolower($tag->htmlElement->name) === 'form'
33+
? Nodes\FormNNameNode::create($tag)
34+
: Nodes\FieldNNameNode::create($tag);
35+
},
3336
];
3437
}
3538

src/Bridges/FormsLatte/Nodes/FieldNNameNode.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function print(PrintContext $context): string
6060
private function init(Tag $tag): void
6161
{
6262
$el = $tag->htmlElement;
63+
assert($el !== null);
6364
$usedAttributes = self::findUsedAttributes($el);
6465
$elName = strtolower($el->name);
6566

src/Bridges/FormsLatte/Nodes/FormNNameNode.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,15 @@ public function print(PrintContext $context): string
5959
private function init(Tag $tag): void
6060
{
6161
$el = $tag->htmlElement;
62+
assert($el !== null);
6263

6364
$tag->replaceNAttribute(new AuxiliaryNode(fn(PrintContext $context) => $context->format(
6465
'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), %dump, false) %line;',
6566
array_fill_keys(FieldNNameNode::findUsedAttributes($el), null),
6667
$this->position,
6768
)));
6869

70+
assert($el->content !== null);
6971
$el->content = new Latte\Compiler\Nodes\FragmentNode([
7072
$el->content,
7173
new AuxiliaryNode(fn(PrintContext $context) => $context->format(

src/Bridges/FormsLatte/Runtime.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static function renderFormBegin(Form $form, array $attrs, bool $withTags
4444
$el->action = (string) $el->action;
4545
$el = clone $el;
4646
if ($form->isMethod('get')) {
47-
$el->action = preg_replace('~\?[^#]*~', '', $el->action, 1);
47+
$el->action = preg_replace('~\?[^#]*~', '', (string) $el->action, 1);
4848
}
4949

5050
$el->addAttributes($attrs);
@@ -59,7 +59,7 @@ public static function renderFormEnd(Form $form, bool $withTags = true): string
5959
{
6060
$s = '';
6161
if ($form->isMethod('get')) {
62-
foreach (preg_split('#[;&]#', (string) parse_url($form->getElementPrototype()->action, PHP_URL_QUERY), -1, PREG_SPLIT_NO_EMPTY) as $param) {
62+
foreach (preg_split('#[;&]#', (string) parse_url((string) $form->getElementPrototype()->action, PHP_URL_QUERY), -1, PREG_SPLIT_NO_EMPTY) as $param) {
6363
$parts = explode('=', $param, 2);
6464
$name = urldecode($parts[0]);
6565
$prefix = explode('[', $name, 2)[0];

src/Forms/Blueprint.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ public function generateLatte(Form $form): string
8585
{
8686
$dict = new \SplObjectStorage;
8787
$dummyForm = new class extends Form {
88-
protected function receiveHttpData(): ?array
88+
protected function receiveHttpData(): array
8989
{
9090
return [];
9191
}
9292
};
9393

9494
foreach ($form->getControls() as $input) {
9595
$dict[$input] = $dummyInput = new class extends Controls\BaseControl {
96-
public $inner;
96+
public Control $inner;
9797

9898

9999
public function getLabel(string|\Stringable|null $caption = null): Html|string|null
@@ -206,7 +206,7 @@ public function generateDataClass(
206206
$type = '?' . $type;
207207
}
208208
} elseif ($input instanceof Container) {
209-
$type = $baseName . ucwords($name);
209+
$type = $baseName . ucwords((string) $name);
210210
$nextCode .= $this->generateDataClass($input, $propertyPromotion, $type);
211211
$type .= self::ClassNameSuffix;
212212
} else {

src/Forms/Container.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public function getUntrustedValues(string|object|null $returnType = null, ?array
138138

139139
} else {
140140
$returnType ??= $this->mappedType ?? ArrayHash::class;
141+
/** @var class-string|'array' $returnType */
141142
$rc = new \ReflectionClass($returnType === self::Array ? \stdClass::class : $returnType);
142143
$constructor = $rc->hasMethod('__construct') ? $rc->getMethod('__construct') : null;
143144
if ($constructor?->getNumberOfRequiredParameters()) {
@@ -261,7 +262,7 @@ public function getErrors(): array
261262
$errors = array_merge($errors, $control->getErrors());
262263
}
263264

264-
return array_unique($errors);
265+
return array_values(array_unique($errors));
265266
}
266267

267268

@@ -295,7 +296,10 @@ public function addComponent(
295296
): static
296297
{
297298
parent::addComponent($component, $name, $insertBefore);
298-
$this->currentGroup?->add($component);
299+
if ($component instanceof Control || $component instanceof self) {
300+
$this->currentGroup?->add($component);
301+
}
302+
299303
return $this;
300304
}
301305

src/Forms/ControlGroup.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
namespace Nette\Forms;
99

10-
use Nette;
1110
use function func_num_args;
1211

1312

@@ -38,7 +37,9 @@ public function add(Control|Container|iterable ...$items): static
3837

3938
} elseif ($item instanceof Container) {
4039
foreach ($item->getComponents() as $component) {
41-
$this->add($component);
40+
if ($component instanceof Control || $component instanceof Container) {
41+
$this->add($component);
42+
}
4243
}
4344
} else {
4445
$this->add(...$item);

src/Forms/Controls/BaseControl.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,20 +250,22 @@ public function getLabel(string|Stringable|null $caption = null)
250250
$label->for = $this->getHtmlId();
251251
$caption ??= $this->caption;
252252
$translator = $this->getForm()->getTranslator();
253-
$label->setText($translator && !$caption instanceof Nette\HtmlStringable ? $translator->translate($caption) : $caption);
253+
$label->setText($translator && $caption !== null && !$caption instanceof Nette\HtmlStringable ? $translator->translate($caption) : $caption);
254254
return $label;
255255
}
256256

257257

258258
public function getControlPart(): ?Html
259259
{
260-
return $this->getControl();
260+
$control = $this->getControl();
261+
return $control instanceof Html ? $control : null;
261262
}
262263

263264

264265
public function getLabelPart(): ?Html
265266
{
266-
return $this->getLabel();
267+
$label = $this->getLabel();
268+
return $label instanceof Html ? $label : null;
267269
}
268270

269271

@@ -362,7 +364,7 @@ public function getTranslator(): ?Nette\Localization\Translator
362364
: null;
363365
}
364366

365-
return $this->translator;
367+
return $this->translator ?: null;
366368
}
367369

368370

@@ -496,7 +498,7 @@ public function getError(): ?string
496498
*/
497499
public function getErrors(): array
498500
{
499-
return array_unique($this->errors);
501+
return array_values(array_unique($this->errors));
500502
}
501503

502504

0 commit comments

Comments
 (0)