Skip to content

Commit c650789

Browse files
committed
Merge branch 'cleanup'
2 parents a8ece0e + 6fe91e5 commit c650789

File tree

50 files changed

+1741
-1407
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1741
-1407
lines changed

.php-cs-fixer.dist.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
'param-out',
7676
'return',
7777
'throws',
78+
'codeCoverageIgnore',
7879
]],
7980
// 'phpdoc_param_order' => true,
8081
'phpdoc_separation' => ['groups' => [
@@ -87,6 +88,7 @@
8788
['phpstan-require-extends', 'phpstan-require-implements'],
8889
['readonly', 'var', 'param', 'param-out', 'return', 'throws', 'phpstan-var', 'phpstan-param', 'phpstan-return', 'phpstan-assert*', 'phpstan-ignore*', 'disregard'],
8990
['phpstan-*'],
91+
['codeCoverageIgnore'],
9092
]],
9193
'phpdoc_tag_casing' => true,
9294
'phpdoc_trim_consecutive_blank_line_separation' => true,

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ a standalone dependency injection container that will be removed when
5252
| HTTP | [salient/http][] | HTTP message and utility classes |
5353
| Iterators | [salient/iterators][] | Iterator classes and traits |
5454
| PHPDoc | [salient/phpdoc][] | A PHPDoc extractor and parser |
55-
| PHPStan | [salient/phpstan][][^1] | PHPStan extensions |
55+
| PHPStan | [salient/phpstan][]\* | PHPStan extensions for development with the Salient toolkit |
5656
| Polyfills | [salient/polyfills][] | Polyfills |
57-
| Sli | [salient/sli][][^1] | `sli`, the toolkit's CLI utility |
57+
| Sli | [salient/sli][]\* | `sli`, the toolkit's CLI utility |
5858
| Sync | [salient/sync][] | A framework and SQLite-backed store for synchronising data with backends |
59-
| Testing | [salient/testing][][^1] | Classes that are useful in test suites |
59+
| Testing | [salient/testing][]\* | Classes that are useful in test suites |
6060
| Utils | [salient/utils][] | Utility methods via stateless classes |
6161

62-
[^1]: This component should only be installed as a development dependency.
62+
<small>\* for development only</small>
6363

6464
## Documentation
6565

phpstan.neon.dist

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,39 +43,47 @@ parameters:
4343
-
4444
identifier: salient.needless.coalesce
4545
paths:
46-
- tests/unit/Toolkit/PHPStan/Utility/Rules/GetCoalesceRuleFailures.php
47-
- tests/unit/Toolkit/PHPStan/Utility/Type/GetCoalesceReturnTypeExtensionAssertions.php
46+
- tests/unit/Toolkit/PHPStan/Utility/GetCoalesceRuleFailures.php
47+
- tests/unit/Toolkit/PHPStan/Utility/data/GetCoalesceReturnTypeExtensionAssertions.php
48+
-
49+
identifier: method.notFound
50+
paths:
51+
- tests/unit/Toolkit/PHPStan/Core/ImmutableTraitRuleFailures.php
52+
-
53+
identifier: property.unused
54+
paths:
55+
- tests/unit/Toolkit/PHPStan/Core/ImmutableTraitRuleFailures.php
4856
-
4957
identifier: salient.property.notFound
5058
paths:
51-
- tests/unit/Toolkit/PHPStan/Core/Rules/TypesAssignedByHasMutatorRuleFailures.php
59+
- tests/unit/Toolkit/PHPStan/Core/ImmutableTraitRuleFailures.php
5260
-
5361
identifier: salient.property.private
5462
paths:
55-
- tests/unit/Toolkit/PHPStan/Core/Rules/TypesAssignedByHasMutatorRuleFailures.php
63+
- tests/unit/Toolkit/PHPStan/Core/ImmutableTraitRuleFailures.php
5664
-
5765
identifier: salient.property.type
5866
paths:
59-
- tests/unit/Toolkit/PHPStan/Core/Rules/TypesAssignedByHasMutatorRuleFailures.php
67+
- tests/unit/Toolkit/PHPStan/Core/ImmutableTraitRuleFailures.php
6068
-
6169
identifier: arguments.count
6270
paths:
63-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrExtendReturnTypeExtensionAssertions.php
64-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrFlattenReturnTypeExtensionAssertions.php
65-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrWhereNotEmptyReturnTypeExtensionAssertions.php
66-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrWhereNotNullReturnTypeExtensionAssertions.php
71+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrExtendReturnTypeExtensionAssertions.php
72+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrFlattenReturnTypeExtensionAssertions.php
73+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrWhereNotEmptyReturnTypeExtensionAssertions.php
74+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrWhereNotNullReturnTypeExtensionAssertions.php
6775
-
6876
identifier: argument.templateType
6977
paths:
70-
- tests/unit/Toolkit/PHPStan/Utility/Rules/GetCoalesceRuleFailures.php
71-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrExtendReturnTypeExtensionAssertions.php
72-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrWhereNotEmptyReturnTypeExtensionAssertions.php
73-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrWhereNotNullReturnTypeExtensionAssertions.php
74-
- tests/unit/Toolkit/PHPStan/Utility/Type/GetCoalesceReturnTypeExtensionAssertions.php
78+
- tests/unit/Toolkit/PHPStan/Utility/GetCoalesceRuleFailures.php
79+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrExtendReturnTypeExtensionAssertions.php
80+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrWhereNotEmptyReturnTypeExtensionAssertions.php
81+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrWhereNotNullReturnTypeExtensionAssertions.php
82+
- tests/unit/Toolkit/PHPStan/Utility/data/GetCoalesceReturnTypeExtensionAssertions.php
7583
-
7684
identifier: argument.type
7785
paths:
78-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrExtendReturnTypeExtensionAssertions.php
79-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrFlattenReturnTypeExtensionAssertions.php
80-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrWhereNotEmptyReturnTypeExtensionAssertions.php
81-
- tests/unit/Toolkit/PHPStan/Utility/Type/ArrWhereNotNullReturnTypeExtensionAssertions.php
86+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrExtendReturnTypeExtensionAssertions.php
87+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrFlattenReturnTypeExtensionAssertions.php
88+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrWhereNotEmptyReturnTypeExtensionAssertions.php
89+
- tests/unit/Toolkit/PHPStan/Utility/data/ArrWhereNotNullReturnTypeExtensionAssertions.php

src/Toolkit/Cache/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515

1616
- Implements [PSR-16 (Common Interface for Caching Libraries)][PSR-16]
1717
- Multiple cache operations can be grouped into an atomic transaction via a
18-
time-bound instance of the cache[^1] that maintains an exclusive lock on the
19-
underlying database until it goes out of scope or is explicitly closed
20-
21-
[^1]: See [CacheStore::asOfNow()][asOfNow] for more information.
18+
[time-bound instance of the cache][asOfNow] that maintains an exclusive lock
19+
on the underlying database until it goes out of scope or is explicitly closed
2220

2321
[asOfNow]:
2422
https://salient-labs.github.io/toolkit/Salient.Cache.CacheStore.html#_asOfNow

src/Toolkit/Collection/README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
values to be accessed via array-like objects with chainable methods.
1616

1717
- Collections are immutable except when array syntax is used to set or unset
18-
items[^immutable]
18+
items
1919
- Use `Collection<TKey of array-key,TValue>` or `ListCollection<TValue>` with
2020
values of any type, or extend them to add type-specific behaviour
2121
- Remix `CollectionTrait`, `ListCollectionTrait`, `ReadOnlyCollectionTrait` and
@@ -49,10 +49,6 @@ Output:
4949
Items in collection: 3
5050
```
5151

52-
[^immutable]:
53-
Methods other than `offsetSet()` and `offsetUnset()` return a modified
54-
instance if the state of the collection changes.
55-
5652
## Documentation
5753

5854
[API documentation][api-docs] for `salient/collections` tracks the `main` branch

src/Toolkit/Curler/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
`DELETE` requests
1818
- Flexible query string and payload handling
1919
- Uses generators to iterate over data from endpoints that use pagination
20-
- Response cache for `HEAD`, `GET` and optionally `POST` requests[^cache]
21-
- Cookie handling and persistence
20+
- Optional response cache for `HEAD`, `GET` and repeatable `POST` requests
21+
- Optional cookie handling and persistence
2222
- Uses [PSR-7][] request, response and stream interfaces
2323
- Implements [PSR-18 (HTTP Client)][PSR-18]
2424
- Behaviour can be customised via stackable middleware
@@ -30,8 +30,6 @@ $curler = new \Salient\Curler\Curler('https://api.github.com/repos/salient-labs/
3030
echo 'Latest release: ' . $curler->get()['tag_name'] . \PHP_EOL;
3131
```
3232

33-
[^cache]: HTTP caching headers are ignored. USE RESPONSIBLY.
34-
3533
[har]: http://www.softwareishard.com/blog/har-12-spec/
3634
[PSR-18]: https://www.php-fig.org/psr/psr-18/
3735
[PSR-7]: https://www.php-fig.org/psr/psr-7/

src/Toolkit/PHPDoc/PHPDocUtil.php

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Salient\Utility\Regex;
1111
use Salient\Utility\Str;
1212
use InvalidArgumentException;
13-
use LogicException;
1413
use ReflectionClass;
1514
use ReflectionClassConstant;
1615
use ReflectionIntersectionType;
@@ -1034,23 +1033,9 @@ private static function isMethodInClass(
10341033
ReflectionClass $class,
10351034
string $name
10361035
): bool {
1037-
if ($method->isInternal() && !$class->isInternal()) {
1038-
return false;
1039-
}
1040-
1041-
$traits = $class->getTraits();
1042-
if (!$traits) {
1043-
return true;
1044-
}
1036+
$result = Reflect::isMethodInClass($method, $class, $name);
10451037

1046-
$location = [
1047-
$method->getFileName(),
1048-
$method->getStartLine(),
1049-
$class->getStartLine(),
1050-
$class->getEndLine(),
1051-
];
1052-
1053-
if (in_array(false, $location, true)) {
1038+
if ($result === null) {
10541039
// @codeCoverageIgnoreStart
10551040
throw new ShouldNotHappenException(sprintf(
10561041
'Unable to check method location: %s::%s()',
@@ -1060,44 +1045,7 @@ private static function isMethodInClass(
10601045
// @codeCoverageIgnoreEnd
10611046
}
10621047

1063-
[$file, $line, $start, $end] = $location;
1064-
1065-
if (
1066-
$file !== $class->getFileName()
1067-
|| $line < $start
1068-
|| $line > $end
1069-
) {
1070-
return false;
1071-
}
1072-
1073-
if ($line > $start && $line < $end) {
1074-
return true;
1075-
}
1076-
1077-
// Check if the method belongs to an adjacent trait on the same line
1078-
if ($inserted = Reflect::getTraitAliases($class)[$name] ?? null) {
1079-
$traits = array_intersect_key($traits, [$inserted[0] => null]);
1080-
$name = $inserted[1];
1081-
}
1082-
foreach ($traits as $trait) {
1083-
if (
1084-
$trait->hasMethod($name)
1085-
&& ($traitMethod = $trait->getMethod($name))->getFileName() === $file
1086-
&& $traitMethod->getStartLine() === $line
1087-
) {
1088-
throw new LogicException(sprintf(
1089-
'Unable to check location of %s::%s(): %s::%s() declared on same line',
1090-
$class->getName(),
1091-
$method->getName(),
1092-
$traitMethod->getDeclaringClass()->getName(),
1093-
$name,
1094-
));
1095-
}
1096-
}
1097-
1098-
// @codeCoverageIgnoreStart
1099-
return true;
1100-
// @codeCoverageIgnoreEnd
1048+
return $result;
11011049
}
11021050

11031051
/**
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Salient\PHPStan\Core;
4+
5+
use PHPStan\Reflection\PropertyReflection;
6+
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
7+
use Salient\Core\Concern\ImmutableTrait;
8+
use Salient\Utility\Reflect;
9+
10+
/**
11+
* @internal
12+
*
13+
* @codeCoverageIgnore
14+
*/
15+
class ImmutableTraitReadWritePropertiesExtension implements ReadWritePropertiesExtension
16+
{
17+
public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
18+
{
19+
return $this->propertyIsMutable($property);
20+
}
21+
22+
public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
23+
{
24+
return $this->propertyIsMutable($property);
25+
}
26+
27+
public function isInitialized(PropertyReflection $property, string $propertyName): bool
28+
{
29+
return $this->propertyIsMutable($property);
30+
}
31+
32+
private function propertyIsMutable(PropertyReflection $property): bool
33+
{
34+
if (!$property->isStatic()) {
35+
$traits = Reflect::getAllTraits(
36+
$property->getDeclaringClass()->getNativeReflection(),
37+
!$property->isPrivate(),
38+
);
39+
foreach (array_keys($traits) as $trait) {
40+
if ($trait === ImmutableTrait::class) {
41+
return true;
42+
}
43+
}
44+
}
45+
return false;
46+
}
47+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Salient\PHPStan\Core;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PhpParser\Node;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Type\NeverType;
11+
use PHPStan\Type\VerbosityLevel;
12+
use Salient\Core\Concern\ImmutableTrait;
13+
use Salient\PHPStan\Internal\RuleTrait;
14+
15+
/**
16+
* @internal
17+
*
18+
* @implements Rule<MethodCall>
19+
*/
20+
class ImmutableTraitRule implements Rule
21+
{
22+
use RuleTrait;
23+
24+
public function getNodeType(): string
25+
{
26+
return MethodCall::class;
27+
}
28+
29+
public function processNode(Node $node, Scope $scope): array
30+
{
31+
if (
32+
($call = $this->getTraitMethodCall($node, $scope, ImmutableTrait::class))
33+
&& ($call->MethodName === 'with' || $call->MethodName === 'without')
34+
&& ($args = $node->getArgs())
35+
&& ($propertyName = $scope->getType($args[0]->value))->isConstantScalarValue()->yes()
36+
&& $propertyName->isString()->yes()
37+
) {
38+
/** @var string */
39+
$propertyName = $propertyName->getConstantScalarValues()[0];
40+
$calledOn = $call->CalledOn;
41+
$has = $calledOn->hasProperty($propertyName);
42+
if (!$has->yes()) {
43+
return [
44+
RuleErrorBuilder::message(sprintf(
45+
'Access to an undefined property %s::$%s.',
46+
$calledOn->describe(VerbosityLevel::typeOnly()),
47+
$propertyName,
48+
))
49+
->identifier('salient.property.notFound')
50+
->build(),
51+
];
52+
}
53+
$property = $calledOn->getProperty($propertyName, $scope);
54+
if (
55+
$property->isPrivate()
56+
&& $calledOn->getObjectClassNames() !== [$call->MethodClass->getName()]
57+
) {
58+
return [
59+
RuleErrorBuilder::message(sprintf(
60+
'Access to an inaccessible property %s::$%s.',
61+
$calledOn->describe(VerbosityLevel::typeOnly()),
62+
$propertyName,
63+
))
64+
->identifier('salient.property.private')
65+
->tip(sprintf(
66+
'Insert %s or change the visibility of the property.',
67+
ImmutableTrait::class,
68+
))
69+
->build(),
70+
];
71+
}
72+
if ($call->MethodName === 'with' && count($args) > 1) {
73+
$propertyType = $property->getWritableType();
74+
if ($propertyType instanceof NeverType) {
75+
$propertyType = $property->getReadableType();
76+
}
77+
$valueType = $scope->getType($args[1]->value);
78+
$accepts = $propertyType->isSuperTypeOf($valueType);
79+
if (!$accepts->yes()) {
80+
return [
81+
RuleErrorBuilder::message(sprintf(
82+
'Property %s::$%s (%s) does not accept %s.',
83+
$calledOn->describe(VerbosityLevel::typeOnly()),
84+
$propertyName,
85+
$propertyType->describe(VerbosityLevel::precise()),
86+
$valueType->describe(VerbosityLevel::precise()),
87+
))
88+
->identifier('salient.property.type')
89+
->build(),
90+
];
91+
}
92+
}
93+
}
94+
return [];
95+
}
96+
}

0 commit comments

Comments
 (0)