Skip to content

Commit 2105106

Browse files
committed
Improve test coverage
1 parent d23c711 commit 2105106

3 files changed

Lines changed: 32 additions & 63 deletions

File tree

src/Validator/IdentifierValidator.php

Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ public function validate(mixed $value, TypeNode $node, string $context, TypeVali
2121
/** @var IdentifierTypeNode $identifierNode */
2222
$identifierNode = $node;
2323
$name = $identifierNode->name;
24+
$lower = strtolower($name);
2425

25-
$ok = match ($name) {
26+
$ok = match ($lower) {
2627
'int', 'integer' => \is_int($value),
2728
'string' => \is_string($value),
2829
'float', 'double' => \is_float($value) || \is_int($value),
@@ -74,8 +75,7 @@ public function validate(mixed $value, TypeNode $node, string $context, TypeVali
7475
'falsy', 'falsey' => (bool) $value === false,
7576
'open-resource' => \is_resource($value),
7677
'closed-resource' => ! \is_resource($value) && get_debug_type($value) === 'resource (closed)',
77-
78-
default => $this->validateCaseInsensitiveOrClass($value, $name),
78+
default => $this->validateClassOrIgnore($value, $name),
7979
};
8080

8181
if (! $ok) {
@@ -85,66 +85,6 @@ public function validate(mixed $value, TypeNode $node, string $context, TypeVali
8585
return null;
8686
}
8787

88-
private function validateCaseInsensitiveOrClass(mixed $value, string $name): bool
89-
{
90-
$lower = strtolower($name);
91-
92-
return match ($lower) {
93-
'int', 'integer' => \is_int($value),
94-
'string' => \is_string($value),
95-
'float', 'double' => \is_float($value) || \is_int($value),
96-
'bool', 'boolean' => \is_bool($value),
97-
'array' => \is_array($value),
98-
'list' => \is_array($value) && (\count($value) === 0 || array_is_list($value)),
99-
'object', 'self', 'static', 'parent', '$this' => \is_object($value),
100-
'callable', 'pure-callable' => \is_callable($value),
101-
'iterable' => is_iterable($value),
102-
'resource' => \is_resource($value),
103-
'null' => $value === null,
104-
'true' => $value === true,
105-
'false' => $value === false,
106-
'mixed' => true,
107-
'scalar' => \is_scalar($value),
108-
'void' => $value === null,
109-
'never', 'never-return', 'never-returns', 'no-return' => false,
110-
'positive-int' => \is_int($value) && $value > 0,
111-
'negative-int' => \is_int($value) && $value < 0,
112-
'non-positive-int' => \is_int($value) && $value <= 0,
113-
'non-negative-int' => \is_int($value) && $value >= 0,
114-
'non-zero-int' => \is_int($value) && $value !== 0,
115-
'unsigned-int' => \is_int($value) && $value >= 0,
116-
'positive-float' => (\is_float($value) || \is_int($value)) && $value > 0,
117-
'negative-float' => (\is_float($value) || \is_int($value)) && $value < 0,
118-
'non-positive-float' => (\is_float($value) || \is_int($value)) && $value <= 0,
119-
'non-negative-float' => (\is_float($value) || \is_int($value)) && $value >= 0,
120-
'non-zero-float' => (\is_float($value) || \is_int($value)) && $value !== 0 && $value !== 0.0,
121-
'class-string' => \is_string($value)
122-
&& ClassNameValidator::isValid($value)
123-
&& (class_exists($value) || interface_exists($value) || trait_exists($value) || enum_exists($value)),
124-
'interface-string' => \is_string($value) && interface_exists($value),
125-
'trait-string' => \is_string($value) && trait_exists($value),
126-
'enum-string' => \is_string($value) && enum_exists($value),
127-
'callable-string' => \is_string($value) && \is_callable($value),
128-
'numeric-string' => \is_string($value) && is_numeric($value),
129-
'non-empty-string' => \is_string($value) && $value !== '',
130-
'lowercase-string' => \is_string($value) && strtolower($value) === $value,
131-
'non-empty-lowercase-string' => \is_string($value) && $value !== '' && strtolower($value) === $value,
132-
'uppercase-string' => \is_string($value) && strtoupper($value) === $value,
133-
'non-empty-uppercase-string' => \is_string($value) && $value !== '' && strtoupper($value) === $value,
134-
'array-key' => \is_int($value) || \is_string($value),
135-
'literal-string' => \is_string($value),
136-
'truthy-string', 'non-falsy-string' => \is_string($value) && (bool) $value === true,
137-
'non-empty-array' => \is_array($value) && \count($value) > 0,
138-
'non-empty-list' => \is_array($value) && \count($value) > 0 && array_is_list($value),
139-
'number', 'numeric' => \is_int($value) || \is_float($value) || (\is_string($value) && is_numeric($value)),
140-
'truthy' => (bool) $value === true,
141-
'falsy', 'falsey' => (bool) $value === false,
142-
'open-resource' => \is_resource($value),
143-
'closed-resource' => ! \is_resource($value) && get_debug_type($value) === 'resource (closed)',
144-
default => $this->validateClassOrIgnore($value, $name),
145-
};
146-
}
147-
14888
private function validateClassOrIgnore(mixed $value, string $name): bool
14989
{
15090
if (! ClassNameValidator::isValid($name)) {

tests/Resolver/TemplateManagerTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,11 @@
235235
;
236236
});
237237
});
238+
239+
test('resolveInheritedTemplates returns safely when class does not exist in reflection', function () {
240+
$anonObj = new stdClass();
241+
TemplateManager::resolveInheritedTemplates($anonObj, 'NonExistentClass12345');
242+
243+
expect(TemplateManager::getBoundTemplatesForInstance($anonObj))->toBeEmpty();
244+
});
238245
});

tests/RuntimeChecker/ReturnCheckerTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@
6363
Config::reset();
6464
}
6565
});
66+
67+
test('returns value cleanly when conditional return references non-existent method or class', function () {
68+
$registry = new TypeValidatorRegistry();
69+
$conditional = new PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode(
70+
'$flag',
71+
new PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode('true'),
72+
new PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode('int'),
73+
new PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode('string'),
74+
false
75+
);
76+
77+
$result = ReturnChecker::checkReturn(
78+
'NonExistentClass123::nonExistentMethod',
79+
'hello',
80+
null,
81+
['otherParam' => true],
82+
$registry,
83+
fn () => null
84+
);
85+
86+
expect($result)->toBe('hello');
87+
});
6688
});
6789

6890
describe('$this Identity Constraints', function () {

0 commit comments

Comments
 (0)