Skip to content

Commit e068217

Browse files
authored
Merge pull request #87 from sasezaki/context-type-2
ContextType Rules - take 2
2 parents 17bb014 + c9cc268 commit e068217

18 files changed

+171
-75
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ includes:
4040
- vendor/struggle-for-php/sfp-phpstan-psr-log/extension.neon
4141
```
4242

43-
and, set parameters `enableLogLevelMethodRule` and `enableContextTypeRule`
43+
and, set parameters `enableLogMethodLevelRule` and `enableContextTypeRule`
4444

4545
```neon
4646
parameters:
4747
sfpPsrLog:
48-
enableLogLevelMethodRule: true # default:false
48+
enableLogMethodLevelRule: true # default:false
4949
enableContextTypeRule: true # default:false
5050
```
5151

composer-require-checker.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"PHPStan\\Type\\FloatType",
2626
"PHPStan\\Type\\IntegerType",
2727
"PHPStan\\Type\\StringType",
28-
"PHPStan\\Type\\TypeCombinator"
28+
"PHPStan\\Type\\TypeCombinator",
29+
"PHPStan\\Reflection\\ClassReflection"
2930
]
3031
}

example/phpstan.default.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \
55
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"./vendor/bin/phpstan analyse -c ./example/phpstan.default.neon --no-progress --error-format=junit | xmllint --format -"}'
66
# docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \
7-
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.default.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.output"}'
7+
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.default.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.default.output"}'
88

99
parameters:
1010
level: 5

example/phpstan.enableContextTypeRule.neon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \
55
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"./vendor/bin/phpstan analyse -c ./example/phpstan.enableContextTypeRule.neon --no-progress --error-format=junit | xmllint --format -"}'
66
# docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \
7-
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.enableContextTypeRule.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.output"}'
7+
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.enableContextTypeRule.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.enableContextTypeRule.output"}'
88

99
parameters:
1010
level: 5
@@ -13,7 +13,7 @@ parameters:
1313
paths:
1414
- %currentWorkingDirectory%/example/src
1515
sfpPsrLog:
16-
enableLogLevelMethodRule: true
16+
enableLogMethodLevelRule: true
1717
enableContextTypeRule: true
1818

1919
# services:

example/phpstan.recommend.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \
55
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"./vendor/bin/phpstan analyse -c ./example/phpstan.recommend.neon --no-progress --error-format=junit | xmllint --format -"}'
66
# docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \
7-
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.recommend.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.output"}'
7+
# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.recommend.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.recommend.output"}'
88

99
parameters:
1010
level: 5

phpcs.xml

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,28 @@
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd">
55

6-
<arg name="basepath" value="."/>
7-
<arg name="cache" value=".phpcs-cache"/>
8-
<arg name="colors"/>
9-
<arg name="extensions" value="php"/>
10-
<arg name="parallel" value="80"/>
6+
<arg name="basepath" value="."/>
7+
<arg name="cache" value=".phpcs-cache"/>
8+
<arg name="colors"/>
9+
<arg name="extensions" value="php"/>
10+
<arg name="parallel" value="80"/>
1111

12-
<!-- Show progress -->
13-
<arg value="p"/>
12+
<!-- Show progress -->
13+
<arg value="p"/>
1414

15-
<!-- Paths to check -->
16-
<file>src</file>
17-
<file>test</file>
15+
<!-- Paths to check -->
16+
<file>src</file>
17+
<file>test</file>
1818

19-
<exclude-pattern>test/TypeProvider/data/*</exclude-pattern>
19+
<exclude-pattern>test/TypeProvider/data/*</exclude-pattern>
2020

21-
<!-- Include all rules from Laminas Coding Standard -->
22-
<rule ref="LaminasCodingStandard"/>
21+
<!-- Include all rules from Laminas Coding Standard -->
22+
<rule ref="LaminasCodingStandard">
23+
<exclude name="SlevomatCodingStandard.Commenting.ForbiddenAnnotations" />
24+
</rule>
2325

24-
<rule ref="PSR12">
25-
<exclude name="Generic.Files.LineLength"/>
26-
<exclude name="WebimpressCodingStandard.Formatting.StringClassReference" />
27-
</rule>
26+
<rule ref="PSR12">
27+
<exclude name="Generic.Files.LineLength"/>
28+
<exclude name="WebimpressCodingStandard.Formatting.StringClassReference" />
29+
</rule>
2830
</ruleset>

rules.neon

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ parametersSchema:
44
enableMessageStaticStringRule: bool(),
55
reportContextExceptionLogLevel: schema(string(), nullable()),
66
contextKeyOriginalPattern: schema(string(), nullable()),
7-
enableLogLevelMethodRule: bool()
7+
enableLogMethodLevelRule: bool()
88
enableContextTypeRule: bool()
99
])
1010

@@ -14,7 +14,7 @@ parameters:
1414
enableMessageStaticStringRule: true
1515
reportContextExceptionLogLevel: 'debug'
1616
contextKeyOriginalPattern: null
17-
enableLogLevelMethodRule: false
17+
enableLogMethodLevelRule: false
1818
enableContextTypeRule: false
1919

2020
conditionalTags:
@@ -23,7 +23,7 @@ conditionalTags:
2323
Sfp\PHPStan\Psr\Log\Rules\MessageStaticStringRule:
2424
phpstan.rules.rule: %sfpPsrLog.enableMessageStaticStringRule%
2525
Sfp\PHPStan\Psr\Log\Rules\LogMethodLevelRule:
26-
phpstan.rules.rule: %sfpPsrLog.enableLogLevelMethodRule%
26+
phpstan.rules.rule: %sfpPsrLog.enableLogMethodLevelRule%
2727
Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule:
2828
phpstan.rules.rule: %sfpPsrLog.enableContextTypeRule%
2929

@@ -51,20 +51,25 @@ services:
5151

5252
-
5353
class: Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule
54-
arguments:
55-
contextTypeProvider: @sfpPsrLogContextTypeProvider
5654

57-
sfpPsrLogContextTypeProvider :
58-
class: Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider
59-
arguments:
60-
exceptionClass: '\Throwable'
55+
# -
56+
# class: Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule
57+
# arguments:
58+
# contextTypeProviderResolver: @contextTypeProviderResolver
59+
6160

62-
# # BigQuery
61+
# contextTypeProviderResolver:
62+
63+
# psrLogContextTypeProvider :
64+
# class: Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider
65+
# arguments:
66+
# exceptionClass: '\Exception'
67+
68+
# BigQuery
6369
#
64-
# sfpPsrLogContextTypeProvider :
70+
# psrLogContextTypeProvider :
6571
# class: Sfp\PHPStan\Psr\Log\TypeProvider\BigQueryContextTypeProvider
6672
# arguments:
6773
# schemaFile:
68-
6974
# -
7075
# class: Sfp\PHPStan\Psr\Log\TypeMapping\BigQuery\GenericTableFieldSchemaJsonPayloadTypeMapper

src/Rules/ContextTypeRule.php

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
use PHPStan\Rules\RuleErrorBuilder;
1111
use PHPStan\ShouldNotHappenException;
1212
use PHPStan\Type\ObjectType;
13-
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
1413
use Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider;
14+
use Sfp\PHPStan\Psr\Log\TypeProviderResolver\AnyScopeContextTypeProviderResolver;
15+
use Sfp\PHPStan\Psr\Log\TypeProviderResolver\ContextTypeProviderResolverInterface;
1516

1617
use function count;
1718
use function in_array;
@@ -22,12 +23,12 @@
2223
*/
2324
final class ContextTypeRule implements Rule
2425
{
25-
/** @var ContextTypeProviderInterface */
26-
private $contextTypeProvider;
26+
/** @var ContextTypeProviderResolverInterface */
27+
private $contextTypeProviderResolver;
2728

28-
public function __construct(?ContextTypeProviderInterface $contextTypeProvider = null)
29+
public function __construct(?ContextTypeProviderResolverInterface $contextTypeProviderResolver)
2930
{
30-
$this->contextTypeProvider = $contextTypeProvider ?? new Psr3ContextTypeProvider();
31+
$this->contextTypeProviderResolver = $contextTypeProviderResolver ?? new AnyScopeContextTypeProviderResolver(new Psr3ContextTypeProvider());
3132
}
3233

3334
public function getNodeType(): string
@@ -62,22 +63,6 @@ public function processNode(Node $node, Scope $scope): array
6263

6364
$contextArgumentNo = 1;
6465
if ($methodName === 'log') {
65-
if (count($args) < 2) {
66-
return [];
67-
}
68-
69-
$logLevelType = $scope->getType($args[0]->value);
70-
71-
$logLevels = [];
72-
foreach ($logLevelType->getConstantStrings() as $constantString) {
73-
$logLevels[] = $constantString->getValue();
74-
}
75-
76-
if (count($logLevels) === 0) {
77-
// cant find logLevels
78-
return [];
79-
}
80-
8166
$contextArgumentNo = 2;
8267
} elseif (! in_array($methodName, LogLevelListInterface::LOGGER_LEVEL_METHODS, true)) {
8368
return [];
@@ -87,8 +72,9 @@ public function processNode(Node $node, Scope $scope): array
8772
return [];
8873
}
8974

90-
$expectedContextType = $this->contextTypeProvider->getType();
91-
$argContextType = $scope->getType($args[$contextArgumentNo]->value);
75+
$argContextType = $scope->getType($args[$contextArgumentNo]->value);
76+
77+
$expectedContextType = $this->contextTypeProviderResolver->resolveContextTypeProvider($scope, $argContextType)->getType();
9278

9379
$ret = $expectedContextType->accepts($argContextType, true);
9480

src/Rules/LogMethodLevelRule.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use PHPStan\Type\ObjectType;
1313

1414
use function count;
15-
use function implode;
1615
use function in_array;
1716
use function sprintf;
1817

@@ -22,7 +21,7 @@
2221
final class LogMethodLevelRule implements Rule
2322
{
2423
private const ERROR_INVALID_LEVEL = <<<'MESSAGE'
25-
Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', '%s' given.
24+
Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', %s given.
2625
MESSAGE;
2726

2827
public function getNodeType(): string
@@ -67,12 +66,9 @@ public function processNode(Node $node, Scope $scope): array
6766
}
6867

6968
if (count($logLevels) === 0) {
70-
// cant find logLevels
7169
return [
7270
RuleErrorBuilder::message(
73-
<<<'MESSAGE'
74-
Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning'.
75-
MESSAGE
71+
sprintf(self::ERROR_INVALID_LEVEL, $logLevelType->toPhpDocNode()->__toString())
7672
)->identifier('sfpPsrLog.logMethodLevel')->build(),
7773
];
7874
}
@@ -90,7 +86,7 @@ public function processNode(Node $node, Scope $scope): array
9086

9187
return [
9288
RuleErrorBuilder::message(
93-
sprintf(self::ERROR_INVALID_LEVEL, implode(', ', $invalidLogLevels))
89+
sprintf(self::ERROR_INVALID_LEVEL, $logLevelType->toPhpDocNode()->__toString())
9490
)->identifier('sfpPsrLog.logMethodLevel')->build(),
9591
];
9692
}

src/TypeMapping/BigQuery/TableFieldSchemaJsonPayloadTypeMapperInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/**
1010
* @see https://cloud.google.com/bigquery/docs/reference/rest/v2/tables?hl=en#TableFieldSchema
1111
*
12+
* @api
1213
* @phpstan-type non_record_field_type 'STRING'|'BYTES'|'INTEGER'|'INT64'|'FLOAT'|'FLOAT64'|'BOOLEAN'|'BOOL'|'TIMESTAMP'|'DATE'|'TIME'|'DATETIME'|'GEOGRAPHY'|'NUMERIC'|'BIGNUMERIC'|'JSON'|'RANGE'
1314
* @phpstan-type field_type non_record_field_type|'RECORD'|'STRUCT'
1415
* @phpstan-type schema_item_minimal array{name: string, type: field_type}

src/TypeProvider/ContextTypeProviderInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use PHPStan\Type\Type;
88

9+
/**
10+
* @api
11+
*/
912
interface ContextTypeProviderInterface
1013
{
1114
public function getType(): Type;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sfp\PHPStan\Psr\Log\TypeProviderResolver;
6+
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Type\Type;
9+
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
10+
11+
final class AnyScopeContextTypeProviderResolver implements ContextTypeProviderResolverInterface
12+
{
13+
/** @var ContextTypeProviderInterface */
14+
private $contextTypeProvider;
15+
public function __construct(ContextTypeProviderInterface $contextTypeProvider)
16+
{
17+
$this->contextTypeProvider = $contextTypeProvider;
18+
}
19+
20+
public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface
21+
{
22+
return $this->contextTypeProvider;
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sfp\PHPStan\Psr\Log\TypeProviderResolver;
6+
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Type\Type;
9+
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
10+
11+
interface ContextTypeProviderResolverInterface
12+
{
13+
public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface;
14+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sfp\PHPStan\Psr\Log\TypeProviderResolver;
6+
7+
use LogicException;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\ClassReflection;
10+
use PHPStan\Type\Type;
11+
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
12+
use Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider;
13+
14+
final class LayeredScopeContextTypeProviderResolver implements ContextTypeProviderResolverInterface
15+
{
16+
/** @phpstan-var array<class-string, ContextTypeProviderInterface> */
17+
private $layerSet;
18+
19+
/** @var AnyScopeContextTypeProviderResolver */
20+
private $anyScopeContextTypeProviderResolver;
21+
22+
/** @var bool */
23+
private $fallbackAnyScope;
24+
25+
/**
26+
* @phpstan-param array<class-string, ContextTypeProviderInterface> $layerSet
27+
*/
28+
public function __construct(array $layerSet, bool $fallbackAnyScope = true)
29+
{
30+
$this->layerSet = $layerSet;
31+
$this->fallbackAnyScope = $fallbackAnyScope;
32+
$this->anyScopeContextTypeProviderResolver = new AnyScopeContextTypeProviderResolver(new Psr3ContextTypeProvider());
33+
}
34+
35+
public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface
36+
{
37+
$classReflection = $scope->getClassReflection();
38+
if (! $classReflection instanceof ClassReflection) {
39+
if ($this->fallbackAnyScope) {
40+
return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope, $contextType);
41+
}
42+
throw new LogicException('can not find belongs to ');
43+
}
44+
45+
foreach ($this->layerSet as $interface => $contextTypeProvider) {
46+
if ($classReflection->implementsInterface($interface)) {
47+
return $contextTypeProvider;
48+
}
49+
}
50+
51+
if (! $this->fallbackAnyScope) {
52+
throw new LogicException('can not find belongs to ');
53+
}
54+
55+
return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope, $contextType);
56+
}
57+
}

0 commit comments

Comments
 (0)