Skip to content

Commit 3ffcc02

Browse files
authored
Merge pull request #88 from sasezaki/rule-level
Use RuleLevelHelper to handle checkUnionTypes (level 8)
2 parents e068217 + 481c0c7 commit 3ffcc02

18 files changed

+269
-85
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Psalm coverage](https://shepherd.dev/github/struggle-for-php/sfp-phpstan-psr-log/coverage.svg)](https://shepherd.dev/github/struggle-for-php/sfp-phpstan-psr-log)
66

77
> [!IMPORTANT]
8-
> The next version `0.25.0` will have a BC break. Please refer `Stubs` section.
8+
> The future version `0.25.0` or later will have a BC break. Please refer `Stubs` section.
99
1010
`struggle-for-php/sfp-phpstan-psr-log` is extra strict and opinionated psr/log (psr-3) rules for PHPStan.
1111

@@ -29,9 +29,9 @@ parameters:
2929
## Stubs
3030

3131
> [!IMPORTANT]
32-
> include psr/log stub be planned to dropped in next release.
32+
> include psr/log stub be planned to dropped in comming release.
3333
34-
To try out the changes in the next version,
34+
To try out the changes in the comming version,
3535

3636
DELETE `vendor/struggle-for-php/sfp-phpstan-psr-log/extension.neon` line from your `phpstan.neon`
3737

composer-require-checker.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
"PHPStan\\Type\\IntegerType",
2727
"PHPStan\\Type\\StringType",
2828
"PHPStan\\Type\\TypeCombinator",
29-
"PHPStan\\Reflection\\ClassReflection"
29+
"PHPStan\\Reflection\\ClassReflection",
30+
"PHPStan\\Rules\\RuleLevelHelper",
31+
"PHPStan\\Rules\\RuleLevelHelperAcceptsResult",
32+
"PHPStan\\Type\\UnionType"
3033
]
3134
}

phpcs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<file>src</file>
1717
<file>test</file>
1818

19+
<exclude-pattern>test/Rules/data/*</exclude-pattern>
1920
<exclude-pattern>test/TypeProvider/data/*</exclude-pattern>
2021

2122
<!-- Include all rules from Laminas Coding Standard -->

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ parameters:
55
- test
66
excludePaths:
77
- test/Rules/data/*
8+
reportUnmatchedIgnoredErrors: false
89

910
includes:
1011
- phar://phpstan.phar/conf/bleedingEdge.neon

psalm-baseline.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<files psalm-version="5.26.1@d747f6500b38ac4f7dfc5edbcae6e4b637d7add0">
3+
<file src="src/Rules/ContextTypeRule.php">
4+
<RedundantCondition>
5+
<code><![CDATA[$acceptsResult instanceof RuleLevelHelperAcceptsResult]]></code>
6+
</RedundantCondition>
7+
<TypeDoesNotContainType>
8+
<code><![CDATA[$acceptsResult === true]]></code>
9+
<code><![CDATA[$acceptsResult === true]]></code>
10+
</TypeDoesNotContainType>
11+
</file>
12+
<file src="src/Rules/LogMethodLevelRule.php">
13+
<RedundantCondition>
14+
<code><![CDATA[$acceptsResult instanceof RuleLevelHelperAcceptsResult]]></code>
15+
</RedundantCondition>
16+
<TypeDoesNotContainType>
17+
<code><![CDATA[$acceptsResult === true]]></code>
18+
<code><![CDATA[$acceptsResult === true]]></code>
19+
</TypeDoesNotContainType>
20+
</file>
21+
</files>

psalm5.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
88
findUnusedBaselineEntry="true"
99
findUnusedCode="false"
10+
errorBaseline="psalm-baseline.xml"
1011
>
1112
<projectFiles>
1213
<directory name="src" />

src/Rules/ContextKeyRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function processNode(Node $node, Scope $scope): array
103103
return $errors;
104104
}
105105

106-
return self::originalPatternMatches($constantArrays, $methodName);
106+
return $this->originalPatternMatches($constantArrays, $methodName);
107107
}
108108

109109
/**

src/Rules/ContextTypeRule.php

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Rules\Rule;
1010
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Rules\RuleLevelHelper;
12+
use PHPStan\Rules\RuleLevelHelperAcceptsResult;
1113
use PHPStan\ShouldNotHappenException;
1214
use PHPStan\Type\ObjectType;
1315
use Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider;
@@ -23,11 +25,17 @@
2325
*/
2426
final class ContextTypeRule implements Rule
2527
{
28+
/** @var RuleLevelHelper */
29+
private $ruleLevelHelper;
30+
2631
/** @var ContextTypeProviderResolverInterface */
2732
private $contextTypeProviderResolver;
2833

29-
public function __construct(?ContextTypeProviderResolverInterface $contextTypeProviderResolver)
30-
{
34+
public function __construct(
35+
RuleLevelHelper $ruleLevelHelper,
36+
?ContextTypeProviderResolverInterface $contextTypeProviderResolver
37+
) {
38+
$this->ruleLevelHelper = $ruleLevelHelper;
3139
$this->contextTypeProviderResolver = $contextTypeProviderResolver ?? new AnyScopeContextTypeProviderResolver(new Psr3ContextTypeProvider());
3240
}
3341

@@ -74,11 +82,21 @@ public function processNode(Node $node, Scope $scope): array
7482

7583
$argContextType = $scope->getType($args[$contextArgumentNo]->value);
7684

77-
$expectedContextType = $this->contextTypeProviderResolver->resolveContextTypeProvider($scope, $argContextType)->getType();
78-
79-
$ret = $expectedContextType->accepts($argContextType, true);
80-
81-
if ($ret->yes()) {
85+
$acceptingContextType = $this->contextTypeProviderResolver->resolveContextTypeProvider($scope)->getType();
86+
87+
$acceptsResult = $this->ruleLevelHelper->accepts($acceptingContextType, $argContextType, $scope->isDeclareStrictTypes());
88+
89+
// To support PHPStan 1 & 2 both.
90+
// RuleLevelHelper::accepts() return type changed from bool to RuleLevelHelperAcceptsResult
91+
// https://github.com/phpstan/phpstan/blob/2.1.x/UPGRADING.md
92+
if (
93+
/** @phpstan-ignore identical.alwaysFalse */
94+
$acceptsResult === true ||
95+
(
96+
/** @phpstan-ignore phpstanApi.class, instanceof.alwaysFalse, booleanAnd.alwaysFalse, identical.alwaysFalse, instanceof.alwaysTrue */
97+
$acceptsResult instanceof RuleLevelHelperAcceptsResult && $acceptsResult->result === true
98+
)
99+
) {
82100
return [];
83101
}
84102

@@ -88,7 +106,7 @@ public function processNode(Node $node, Scope $scope): array
88106
'Parameter #%d $context of method Psr\Log\LoggerInterface::%s() expects %s, %s given.',
89107
$contextArgumentNo + 1,
90108
$methodName,
91-
(string) $expectedContextType->toPhpDocNode(),
109+
(string) $acceptingContextType->toPhpDocNode(),
92110
(string) $argContextType->toPhpDocNode()
93111
)
94112
)->identifier('sfpPsrLog.contextType')->build(),

src/Rules/LogMethodLevelRule.php

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Rules\Rule;
1010
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Rules\RuleLevelHelper;
12+
use PHPStan\Rules\RuleLevelHelperAcceptsResult;
1113
use PHPStan\ShouldNotHappenException;
14+
use PHPStan\Type\Constant\ConstantStringType;
1215
use PHPStan\Type\ObjectType;
16+
use PHPStan\Type\UnionType;
1317

1418
use function count;
15-
use function in_array;
1619
use function sprintf;
1720

1821
/**
@@ -21,9 +24,30 @@
2124
final class LogMethodLevelRule implements Rule
2225
{
2326
private const ERROR_INVALID_LEVEL = <<<'MESSAGE'
24-
Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', %s given.
27+
Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects %s, %s given.
2528
MESSAGE;
2629

30+
/** @var RuleLevelHelper */
31+
private $ruleLevelHelper;
32+
33+
/** @var UnionType */
34+
private $acceptingLogLevel;
35+
36+
public function __construct(RuleLevelHelper $ruleLevelHelper)
37+
{
38+
$this->ruleLevelHelper = $ruleLevelHelper;
39+
$this->acceptingLogLevel = new UnionType([
40+
new ConstantStringType('emergency'),
41+
new ConstantStringType('alert'),
42+
new ConstantStringType('critical'),
43+
new ConstantStringType('error'),
44+
new ConstantStringType('warning'),
45+
new ConstantStringType('notice'),
46+
new ConstantStringType('info'),
47+
new ConstantStringType('debug'),
48+
]);
49+
}
50+
2751
public function getNodeType(): string
2852
{
2953
return Node\Expr\MethodCall::class;
@@ -58,35 +82,31 @@ public function processNode(Node $node, Scope $scope): array
5882
return [];
5983
}
6084

61-
$logLevelType = $scope->getType($args[0]->value);
62-
63-
$logLevels = [];
64-
foreach ($logLevelType->getConstantStrings() as $constantString) {
65-
$logLevels[] = $constantString->getValue();
66-
}
67-
68-
if (count($logLevels) === 0) {
69-
return [
70-
RuleErrorBuilder::message(
71-
sprintf(self::ERROR_INVALID_LEVEL, $logLevelType->toPhpDocNode()->__toString())
72-
)->identifier('sfpPsrLog.logMethodLevel')->build(),
73-
];
74-
}
75-
76-
$invalidLogLevels = [];
77-
foreach ($logLevels as $logLevel) {
78-
if (! in_array($logLevel, LogLevelListInterface::LOGGER_LEVEL_METHODS, true)) {
79-
$invalidLogLevels[] = $logLevel;
80-
}
81-
}
82-
83-
if (count($invalidLogLevels) === 0) {
85+
$argLevel = $scope->getType($args[0]->value);
86+
87+
$acceptsResult = $this->ruleLevelHelper->accepts($this->acceptingLogLevel, $argLevel, $scope->isDeclareStrictTypes());
88+
89+
// To support PHPStan 1 & 2 both.
90+
// RuleLevelHelper::accepts() return type changed from bool to RuleLevelHelperAcceptsResult
91+
// https://github.com/phpstan/phpstan/blob/2.1.x/UPGRADING.md
92+
if (
93+
/** @phpstan-ignore identical.alwaysFalse */
94+
$acceptsResult === true ||
95+
(
96+
/** @phpstan-ignore phpstanApi.class, instanceof.alwaysFalse, booleanAnd.alwaysFalse, identical.alwaysFalse, instanceof.alwaysTrue */
97+
$acceptsResult instanceof RuleLevelHelperAcceptsResult && $acceptsResult->result === true
98+
)
99+
) {
84100
return [];
85101
}
86102

87103
return [
88104
RuleErrorBuilder::message(
89-
sprintf(self::ERROR_INVALID_LEVEL, $logLevelType->toPhpDocNode()->__toString())
105+
sprintf(
106+
self::ERROR_INVALID_LEVEL,
107+
$this->acceptingLogLevel->toPhpDocNode()->__toString(),
108+
$argLevel->toPhpDocNode()->__toString()
109+
)
90110
)->identifier('sfpPsrLog.logMethodLevel')->build(),
91111
];
92112
}

src/TypeProviderResolver/AnyScopeContextTypeProviderResolver.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Sfp\PHPStan\Psr\Log\TypeProviderResolver;
66

77
use PHPStan\Analyser\Scope;
8-
use PHPStan\Type\Type;
98
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
109

1110
final class AnyScopeContextTypeProviderResolver implements ContextTypeProviderResolverInterface
@@ -17,7 +16,7 @@ public function __construct(ContextTypeProviderInterface $contextTypeProvider)
1716
$this->contextTypeProvider = $contextTypeProvider;
1817
}
1918

20-
public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface
19+
public function resolveContextTypeProvider(Scope $scope): ContextTypeProviderInterface
2120
{
2221
return $this->contextTypeProvider;
2322
}

src/TypeProviderResolver/ContextTypeProviderResolverInterface.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
namespace Sfp\PHPStan\Psr\Log\TypeProviderResolver;
66

77
use PHPStan\Analyser\Scope;
8-
use PHPStan\Type\Type;
98
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
109

1110
interface ContextTypeProviderResolverInterface
1211
{
13-
public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface;
12+
public function resolveContextTypeProvider(Scope $scope): ContextTypeProviderInterface;
1413
}

src/TypeProviderResolver/LayeredScopeContextTypeProviderResolver.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use LogicException;
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Reflection\ClassReflection;
10-
use PHPStan\Type\Type;
1110
use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface;
1211
use Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider;
1312

@@ -32,12 +31,12 @@ public function __construct(array $layerSet, bool $fallbackAnyScope = true)
3231
$this->anyScopeContextTypeProviderResolver = new AnyScopeContextTypeProviderResolver(new Psr3ContextTypeProvider());
3332
}
3433

35-
public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface
34+
public function resolveContextTypeProvider(Scope $scope): ContextTypeProviderInterface
3635
{
3736
$classReflection = $scope->getClassReflection();
3837
if (! $classReflection instanceof ClassReflection) {
3938
if ($this->fallbackAnyScope) {
40-
return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope, $contextType);
39+
return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope);
4140
}
4241
throw new LogicException('can not find belongs to ');
4342
}
@@ -52,6 +51,6 @@ public function resolveContextTypeProvider(Scope $scope, Type $contextType): Con
5251
throw new LogicException('can not find belongs to ');
5352
}
5453

55-
return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope, $contextType);
54+
return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope);
5655
}
5756
}

0 commit comments

Comments
 (0)